設計模式之概述篇

颯沓流星發表於2022-06-30

1、設計模式的本質

物件導向設計原則的實際運用,是對類的封裝性、繼承性和多型性以及類的關聯關係和組合關係的充分理解。

2、設計模式的目的

提高程式碼可讀性、重用性、可靠性、可擴充套件性,實現“高內聚,低耦合”。

名詞解釋

  1. 可讀性:按照規範程式設計,便於其他程式設計師閱讀和理解
  2. 重用性:相同功能的程式碼,可以重複使用,無需多次編寫
  3. 可靠性:增加功能時,對原有功能沒有影響
  4. 可擴充套件性:增加功能時方便,可維護性強

3、設計模式的依據

常見的設計模式有23種,但是發展到今天還有很多叫不上名字來的設計模式,無一例外都遵循著“軟體設計七大原則”。

3.1 單一職責原則(Single Responsibility Principle, SRP)

3.1.1 解釋

​ 單一職責就是一個類或者一個方法只負責一項職責

3.1.2 舉例

​ 假設有一個IT部門,一個開發,一個測試,一個運維。我們把三個人的工作抽象為一個類

3.1.2.1 Demo1
public static void main(String[] args) {
    Employee.work("Developer");
    Employee.work("Tester");
    Employee.work("Operator");
}
// 員工類
static class Employee {
    public static void work(String name) {
        System.out.println(name + "正在寫程式碼...");
    }
}

執行結果

Developer正在寫程式碼...
Tester正在寫程式碼...
Operator正在寫程式碼...

​ 很明顯,開發、測試、運維都在寫程式碼,顯然不合理;正常來說,開發寫程式碼、測試寫用例、運維寫指令碼,而Demo1中的work實現了三種不同職責,違背了單一職責原則

3.1.2.2 Demo2
public static void main(String[] args) {
    Developer.work("Developer");
    Tester.work("Tester");
    Operator.work("Operator");
}
// 員工類:開發
static class Developer {
    public static void work(String name) {
        System.out.println(name + "正在寫程式碼...");
    }
}
// 員工類:測試
static class Tester {
    public static void work(String name) {
        System.out.println(name + "正在寫用例...");
    }
}
// 員工類:運維
static class Operator {
    public static void work(String name) {
        System.out.println(name + "正在寫指令碼...");
    }
}

執行結果

Developer正在寫程式碼...
Tester正在寫用例...
Operator正在寫指令碼...

​ 看執行結果,已經符合了單一職責原則,但是看Demo2程式碼會發現,三種職責我們建立了三個類,把Employee分解為DeveloperTesterOperator,並且呼叫方main也做了修改,這樣改動太大

3.1.2.3 Demo3
public static void main(String[] args) {
    Employee.workCode("Developer");
    Employee.workUseCase("Tester");
    Employee.workScript("Operator");
}
// 員工類
static class Employee {
    public static void workCode(String name) {
        System.out.println(name + "正在寫程式碼...");
    }
    public static void workUseCase(String name) {
        System.out.println(name + "正在寫用例...");
    }
    public static void workScript(String name) {
        System.out.println(name + "正在寫指令碼...");
    }
}

執行結果

Developer正在寫程式碼...
Tester正在寫用例...
Operator正在寫指令碼...

​ 在Demo3中把work一分為三,沒有分解類,而是在方法級別上進行了拆分,也達到了預期的效果,並且呼叫者main中改動量也很小

3.1.3 總結

​ 1. 單一職責可以細化為類級別方法級別,最低限度是在方法級別保證單一職責原則;但是如果一個類中有幾十個方法,那麼就需要衡量下是否需要進行類分解

​ 2. 提高程式碼可讀性,可維護性,可擴充套件性

​ 3. 降低類的複雜度

3.2 介面隔離原則(Interface Segregation Principle,ISP)

3.2.1 解釋

一個類對另一個類的依賴應該建立在最小的介面上,即一個類不應該依賴它不需要的介面

3.2.2 舉例

​ 就拿涼拌黃瓜和炒黃瓜的步驟來舉例

  1. 涼拌黃瓜:洗菜 -> 切菜 -> 涼拌

  2. 炒 黃 瓜:洗菜 -> 切菜 -> 炒菜

3.2.2.1 Demo1
private static final String name = "黃瓜";
public static void main(String[] args) {
    CookingCold cookingCold = new CookingCold();
    ColdMixCucumber coldMixCucumber = new ColdMixCucumber();
    coldMixCucumber.wash(cookingCold);
    coldMixCucumber.cut(cookingCold);
    coldMixCucumber.coldMix(cookingCold);
    System.out.println();
    CookingHot cookingHot = new CookingHot();
    FryCucumber fryCucumber = new FryCucumber();
    fryCucumber.wash(cookingHot);
    fryCucumber.cut(cookingHot);
    fryCucumber.fry(cookingHot);;
}
// 做菜介面
interface Cooking {
    // 洗菜
    void wash(String name);
    // 切菜
    void cut(String name);
    // 涼拌
    void coldMix(String name);
    // 炒菜
    void fry(String name);
}
// 做冷盤
static class CookingCold implements Cooking {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {
        System.out.println("涼拌" + name);
    }
    @Override
    public void fry(String name) {}
}
// 做熱菜
static class CookingHot implements Cooking {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {}
    @Override
    public void fry(String name) {
        System.out.println("炒" + name);
    }
}
// 涼拌黃瓜
static class ColdMixCucumber {
    // 洗黃瓜
    public void wash(Cooking cooking) {
        cooking.wash(name);
    }
    // 切黃瓜
    public void cut(Cooking cooking) {
        cooking.cut(name);
    }
    // 涼拌黃瓜
    public void coldMix(Cooking cooking) {
        cooking.coldMix(name);
    }
}
// 炒黃瓜
static class FryCucumber {
    // 洗黃瓜
    public void wash(Cooking cooking) {
        cooking.wash(name);
    }
    // 切黃瓜
    public void cut(Cooking cooking) {
        cooking.cut(name);
    }
    // 炒黃瓜
    public void fry(Cooking cooking) {
        cooking.fry(name);
    }
}

​ 下圖為Demo1的UML類圖,據此分析CookingColdCookingHot均實現了Cooking介面並實現其所有方法,但在ColdMixCucumberFryCucumber中僅使用了其中的三個方法;雖然執行結果沒有問題,但是明顯Cooking介面的設計不合理,不符合介面隔離原則

設計模式之概述篇
3.2.2.2 Demo2

​ 我們注意到Cooking介面中,washcut是都會用到的,而coldMixfry並不會全部用到;根據介面隔離原則,我們把Cooking介面分解為三個介面,UML類圖如下所示:

設計模式之概述篇
private static final String name = "黃瓜";
public static void main(String[] args) {
    CookingCold cookingCold = new CookingCold();
    ColdMixCucumber coldMixCucumber = new ColdMixCucumber();
    coldMixCucumber.wash(cookingCold);
    coldMixCucumber.cut(cookingCold);
    coldMixCucumber.coldMix(cookingCold);
    System.out.println();
    CookingHot cookingHot = new CookingHot();
    FryCucumber fryCucumber = new FryCucumber();
    fryCucumber.wash(cookingHot);
    fryCucumber.cut(cookingHot);
    fryCucumber.fry(cookingHot);;
}
// 做菜介面_01
interface Cooking_01 {
    // 洗菜
    void wash(String name);
    // 切菜
    void cut(String name);
}
// 做菜介面_02
interface Cooking_02 {
    // 涼拌
    void coldMix(String name);
}
// 做菜介面_03
interface Cooking_03 {
    // 炒菜
    void fry(String name);
}
// 做冷盤
static class CookingCold implements Cooking_01, Cooking_02 {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {
        System.out.println("涼拌" + name);
    }
}
// 做熱菜
static class CookingHot implements Cooking_01, Cooking_03 {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void fry(String name) {
        System.out.println("炒" + name);
    }
}
// 涼拌黃瓜
static class ColdMixCucumber {
    // 洗黃瓜
    public void wash(Cooking_01 cooking) {
        cooking.wash(name);
    }
    // 切黃瓜
    public void cut(Cooking_01 cooking) {
        cooking.cut(name);
    }
    // 涼拌黃瓜
    public void coldMix(Cooking_02 cooking) {
        cooking.coldMix(name);
    }
}
// 炒黃瓜
static class FryCucumber {
    // 洗黃瓜
    public void wash(Cooking_01 cooking) {
        cooking.wash(name);
    }
    // 切黃瓜
    public void cut(Cooking_01 cooking) {
        cooking.cut(name);
    }
    // 炒黃瓜
    public void fry(Cooking_03 cooking) {
        cooking.fry(name);
    }
}

3.2.3 總結

​ 1.提高程式碼可讀性,可重用性,可維護性

​ 3.降低類的複雜度,降低耦合性

3.3 依賴倒置 / 倒轉原則(Dependency Inversion Principle,DIP)

3.3.1 解釋

​ 1. 依賴倒置/倒轉的核心是面向介面程式設計

​ 2. 相對於細節的多變性,抽象的東西相對穩定得多;以抽象為基礎的架構比以細節為基礎的架構穩定得多;而在Java中抽象是指抽象類和介面細節是指抽象類和介面的具體實現

​ 3. 抽象類和介面可以理解為制定規範,但不涉及任何具體操作,把展現細節的工作交給他們具體的實現類完成,以此來提高系統的可靠性和可維護性

3.3.2 舉例

​ 以付款場景為例

3.3.2.1 Demo1
image-20220624175818679
public static void main(String[] args) {
    Person person = new Person();
    person.payment( new Ali() );
    person.payment( new WeChat() );
    person.payment( new BankCard() );
}
static class Person {
    // 使用支付寶付款
    public void payment(Ali type) {
        System.out.println( type.pay() );
    }
    // 使用微信付款
    public void payment(WeChat type) {
        System.out.println( type.pay() );
    }
    // 使用銀行卡付款
    public void payment(BankCard type) {
        System.out.println( type.pay() );
    }
}
static class Ali {
    public String pay() {
        return "通過 -支付寶- 付款";
    }
}
static class WeChat {
    public String pay() {
        return "通過 -微信- 付款";
    }
}
static class BankCard {
    public String pay() {
        return "通過 -銀行卡- 付款";
    }
}
3.3.2.2 Demo2
設計模式之概述篇

3.3.3 總結

​ 依賴關係傳遞的三種方式:介面傳遞、構造方法傳遞、Setter方法傳遞

3.3.3.1 介面傳遞
// 依賴關係傳遞-通過介面傳遞
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    ISwitch s = new Switch();
    s.turnOn(computer);
}
// 電腦介面
interface IComputer {
    // 執行
    void play();
}
// 神舟(炫龍)筆記本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龍)筆記本,正在執行...");
    }
}
// 開關介面
interface ISwitch {
    // 開啟【通過介面傳遞】
    void turnOn(IComputer computer);
}
// 開關實現類
static class Switch implements ISwitch {
    @Override
    public void turnOn(IComputer computer) {
        computer.play();
    }
}
3.3.3.2 構造方法傳遞
// 依賴關係傳遞-通過構造方法傳遞
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    ISwitch s = new Switch(computer);
    s.turnOn();
}
// 電腦介面
interface IComputer {
    // 執行
    void play();
}
// 神舟(炫龍)筆記本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龍)筆記本,正在執行...");
    }
}
// 開關介面
interface ISwitch {
    // 開啟
    void turnOn();
}
// 開關實現類
static class Switch implements ISwitch {
    private final IComputer computer;
    // 【通過構造方法傳遞】
    Switch(IComputer computer) {
        this.computer = computer;
    }
    @Override
    public void turnOn() {
        this.computer.play();
    }
}
3.3.3.3 Setter方法傳遞
// 依賴關係傳遞-通過Setter方法傳遞
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    Switch s = new Switch();
    s.setComputer(computer);
    s.turnOn();
}
// 電腦介面
interface IComputer {
    // 執行
    void play();
}
// 神舟(炫龍)筆記本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龍)筆記本,正在執行...");
    }
}
// 開關介面
interface ISwitch {
    // 開啟
    void turnOn();
}
// 開關實現類
static class Switch implements ISwitch {
    private IComputer computer;
    @Override
    public void turnOn() {
        this.computer.play();
    }
    // 【通過Setter方法傳遞】
    public void setComputer(IComputer computer) {
        this.computer = computer;
    }
}

3.4 裡式替換原則(Liskov Substitution Principle,LSP)

3.4.1 解釋

​ 裡式替換原則規範了繼承的使用:在子類中儘量不要覆寫父類的方法。繼承會使兩個類的耦合性增強,可以改用組合聚合依賴的方式

3.4.2 舉例

3.4.2.1 Demo1
設計模式之概述篇
public static void main(String[] args) {
    Parent parent1 = new Parent();
    parent1.add(1, 2);
    parent1.subtract(1, 2);
    Son son = new Son();
    son.add(1, 2);
    son.subtract(1, 2);
}
static class Parent {
    // 加法運算
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }
    // 減法運算
    public void subtract(int a, int b) {
        System.out.println("a - b = " + (a - b));
    }
}
static class Son extends Parent {
    // 減法運算
    @Override
    public void subtract(int a, int b) {
        System.out.println("a * b = " + (a * b));
    }
}

​ 以上程式碼中子類Son覆寫了父類Parentsubtract變為乘法運算了,但是對於呼叫者main來說,這一過程是透明的,這就導致了程式的錯誤。要解決這個問題,我們可以把有衝突的方法再封裝到另一個父類Base中,讓SonParent繼承這個更基礎的父類Base,原先的繼承關係去掉,使用依賴聚合組合等關係代替。

3.4.2.2 Demo2
設計模式之概述篇
public static void main(String[] args) {
    Parent parent1 = new Parent();
    parent1.add(1, 2);
    parent1.subtract(1, 2);
    Son son = new Son();
    son.add(1, 2);
    // 減法運算:Parent
    son.subtract1(1, 2);
    // 減法運算:Son
    son.subtract2(1, 2);
    son.multiply(1, 2);
}
// 更基礎的類
static class Base {
    // 減法運算
    public void subtract(int a, int b) {
        System.out.println("a - b = " + (a - b));
    }
}
static class Parent extends Base {
    // 加法運算
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }
}
static class Son extends Base {
    private final Parent parent = new Parent();
    // 減法運算:Parent
    public void subtract1(int a, int b) {
        // 呼叫Parent類的subtract方法
        parent.subtract(a, b);
    }
    // 減法運算:Son
    public void subtract2(int a, int b) {
        System.out.println("a - b - 1 = " + (a - b - 1));
    }
    // 加法運算
    public void add(int a, int b) {
        // 呼叫Parent類的add方法
        parent.subtract(a, b);
    }
    // 乘法運算
    public void multiply(int a, int b) {
        System.out.println("a * b = " + (a * b));
    }
}

3.4.3 總結

  1. 克服子類覆寫父類方法導致重用性降低的問題
  2. 保證程式的正確性,對類的擴充套件不會給變動已有系統,降低了程式碼出錯的可能
  3. 加強程式的健壯性,變更的同時也可以兼顧到相容性
  4. 提高程式的可維護性、可擴充套件性

3.5 迪米特法則(Law of Demeter,LOD)

3.5.1 解釋

​ 迪米特法則又叫最少知識原則(Least Knowledge Principle,LKP),即一個類對自己依賴的類知道的越少越好。比如A類中有個B類的成員變數,那麼我們就說A類依賴了B類,對B類來說,不管邏輯有多複雜,都應該儘量將邏輯封裝在B類的內部(除了允許對外訪問的方法);還有另外一種更為直接的定義方式:只與直接的朋友通訊

直接的朋友:出現在類的成員變數方法引數方法返回值中的類為直接的朋友,出現在區域性變數中的類不是直接的朋友。也就是說,陌生類儘量不要以區域性變數的形式出現在類的內部。

3.5.2 舉例

3.5.2.1 Demo1
public static void main(String[] args) {
    School school = new School();
    school.selectAllStudent();
    System.out.println("---------------");
    school.selectAllTeacher();
}
// 學校類
static class School {
    // 查詢所有學生
    public void selectAllStudent() {
        //TODO Student以區域性變數形式出現,不是直接的朋友
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            students.add( new Student(String.valueOf(i)) );
        }
        for (Student student : students) {
            System.out.println(student);
        }
    }
    // 查詢所有老師
    public void selectAllTeacher() {
        //TODO Teacher以區域性變數形式出現,不是直接的朋友
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            teachers.add( new Teacher(String.valueOf(i)) );
        }
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
    }
}
3.5.2.2 Demo2
public static void main(String[] args) {
    School school = new School();
    school.printAllStudent( school.selectAllStudent() );
    System.out.println("---------------");
    school.printAllTeacher( school.selectAllTeacher() );
}
// 學校類
static class School {
    // 查詢所有學生
    public List<Student> selectAllStudent() {
        // Student以方法返回值形式出現,是直接的朋友
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            students.add( new Student(String.valueOf(i)) );
        }
        return students;
    }
    // 列印所有學生
    public void printAllStudent(List<Student> students) {
        // Student方法引數形式出現,是直接的朋友
        for (Student student : students) {
            System.out.println(student);
        }
    }
    // 查詢所有老師
    public List<Teacher> selectAllTeacher() {
        // Teacher以方法返回值形式出現,是直接的朋友
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            teachers.add( new Teacher(String.valueOf(i)) );
        }
        return teachers;
    }
    // 列印所有老師
    public void printAllTeacher(List<Teacher> teachers) {
        // Teacher方法引數形式出現,是直接的朋友
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
    }
}

​ 我們把Demo1中的StudentTeacher區域性變數修改為方法返回值的形式,並且作為printAllStudentprintAllTeacher的方法引數進行後續的列印操作,使得StudentTeacher成為School直接的朋友,降低了類之間耦合性。

3.5.3 總結

​ 迪米特法則核心其實就是降低類之間的耦合

3.6 合成 / 聚合複用原則(Composite / Aggregate Reuse Principle,C / ARP)

3.6.1 解釋

​ 合成複用原則是說盡量使用合成 / 聚合的方式,避免使用繼承,因為繼承相對於合成或者聚合是一種強依賴(如果父類需要修改,那麼就需要考慮是否會對其所有的子類產生影響)

3.6.2 舉例

​ 我們以B類對A類中的test方法做增強為例,分別以繼承組合聚合依賴的方式實現

3.6.2.1 Demo1
設計模式之概述篇
// 繼承關係
public static void main(String[] args) {
    B b = new B();
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 類的 test 方法...");
    }
}
static class B extends A {
    @Override
    public void test() {
        // A類方法
        super.test();
        // B類對A類方法進行增強
        System.out.println("B 類增強後的 test 方法...");
    }
}
3.6.2.2 Demo2
設計模式之概述篇
// 組合關係
public static void main(String[] args) {
    B b = new B();
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 類的 test 方法...");
    }
}
static class B {
    private A a = new A();
    public void test() {
        // A類方法
        a.test();
        // B類對A類方法進行增強
        System.out.println("B 類增強後的 test 方法...");
    }
}
3.6.2.3 Demo3
設計模式之概述篇
// 聚合關係
public static void main(String[] args) {
    B b = new B();
    b.setA( new A() );
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 類的 test 方法...");
    }
}
static class B {
    private A a;
    public void test() {
        // A類方法
        a.test();
        // B類對A類方法進行增強
        System.out.println("B 類增強後的 test 方法...");
    }
    public void setA(A a) {
        this.a = a;
    }
}
3.6.2.4 Demo4
設計模式之概述篇
// 依賴關係
public static void main(String[] args) {
    B b = new B();
    b.test( new A() );
}
static class A {
    public void test() {
        System.out.println("A 類的 test 方法...");
    }
}
static class B {
    public void test(A a) {
        // A類方法
        a.test();
        // B類對A類方法進行增強
        System.out.println("B 類增強後的 test 方法...");
    }
}

3.6.3 總結

  1. 維持類的封裝性
  2. 降低類之間耦合度
  3. 提高可複用性

3.7 開閉原則(Open Closed Principle,OCP)

3.7.1 解釋

​ 把開閉原則放到最後講,是因為開閉原則是七大原則中最基礎,也是最重要的設計原則,其他六種設計原則均是為了實現開閉原則。

​ 一個軟體實體(類,模組,函式)應該保證:對擴充套件開放(提供方),對修改關閉(使用方),用抽象構建框架,用實現擴充套件細節。當軟體需要變化時,儘量通過擴充套件軟體實體的方式來實現變化,而不是通過修改已有的程式碼來實現變化。

3.7.2 舉例

3.7.2.1 Demo1
public static void main(String[] args) {
    PaintBrush brush = new PaintBrush();
    brush.draw( new Triangle() );
    brush.draw( new Rectangle() );
}
static class PaintBrush {
    // 使用方(對修改關閉)
    public void draw(Shape shape) {
        switch (shape.type) {
            case 1:
                drawTriangle();
                break;
            case 2:
                drawRectangle();
                break;
                //TODO 擴充套件分支
        }
    }
    private void drawTriangle() {
        System.out.println("畫三角形");
    }
    private void drawRectangle() {
        System.out.println("畫長方形");
    }
    //TODO 擴充套件方法
}
// 提供方(對擴充套件開放)
static class Shape {
    public int type;
}
static class Triangle extends Shape {
    public Triangle() {
        super.type = 1;
    }
}
static class Rectangle extends Shape {
    public Rectangle() {
        super.type = 2;
    }
}
//TODO 擴充套件類

​ 以上程式碼為例,如果我們想要再新加一個型別,就需要在註釋//TODO的地方做擴充套件,PaintBrush作為使用方也需要做修改,違反了開閉原則

3.7.2.2 Demo2
public static void main(String[] args) {
    PaintBrush brush = new PaintBrush();
    brush.draw( new Triangle() );
    brush.draw( new Rectangle() );
}
static class PaintBrush {
    // 使用方(對修改關閉)
    public void draw(Shape shape) {
        shape.draw();
    }
}
// 提供方(對擴充套件開放)
static abstract class Shape {
    // 抽象方法,由子類實現細節
    public abstract void draw();
}
static class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("畫三角形");
    }
}
static class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("畫長方形");
    }
}
//TODO 擴充套件類

​ 程式碼根據開閉原則優化後,在新增型別時,只需要在提供方Shape新增子類即可,使用方PaintBrush程式碼無需修改

3.7.3 總結

  1. 對軟體測試來說,如果遵守開閉原則,那麼只需要測試擴充套件的部分程式碼,原有程式碼無需測試
  2. 提高程式碼可維護性、可複用性

4、設計模式的工具

​ 提起設計模式,很多人感覺無從下手,因為類與類、類與介面之間的繼承、實現等關係特別複雜,看著看著就被繞進去了,我們藉助一種具象化的工具:UML圖,來幫助我們理解複雜的關係

統一建模語言(Unified Modeling Language,UML)是一種為物件導向系統的產品進行說明、視覺化和編制文件的一種標準語言,是非專利的第三代建模和規約語言。UML是物件導向設計的建模工具,獨立於任何具體程式設計語言。

​ ---- 百度百科

​ UML圖本質就是用一系列的符號來表示軟體模型中各個元素及其之間的關係(比如Java中的類,介面,依賴,實現,泛化,組合,聚合等元素或關係),UML圖型別眾多,這裡只介紹UML類圖

4.1 UML類圖表示方法

​ 下面使用Java程式碼UML類圖對照的方式展示

4.1.1 具體類

// 人
public class Person {
    // 性別
    public String sex;
    // 生日
    protected String birth;
    // 工作
    String work;
    // 年齡
    private short age;
    public String getSex() {
        return sex;
    }
    protected String getBirth() {
        return birth;
    }
    String getWork() {
        return work;
    }
    private short getAge() {
        return age;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    protected void setBirth(String birth) {
        this.birth = birth;
    }
    void setWork(String work) {
        this.work = work;
    }
    private void setAge(short age) {
        this.age = age;
    }
}

​ 具體類在UML類圖中用矩形表示,矩形的第一層是類名第二層是成員變數第三層是成員方法,成員變數和成員方法前的訪問修飾符都是由符號表示

  1. “+” 表示 “public”
  2. “#” 表示 “protected”
  3. “-” 表示 “private”
  4. 不加符號 表示 “default”

​ 方法參數列示為:

method(變數名1:變數型別1, 變數名2:變數型別2, ... , 變數名n:變數型別n):返回值型別

4.1.2 抽象類

// 接收方
public abstract class Receiver {
    // 訊息
    private String message;
    // 獲取接收型別
    protected abstract String getType();
    public String getMessage() {
        return message;
    }
}

​ 抽象類與具體類基本相同,只是矩形第一層的抽象類名是斜體的,成員方法中的抽象方法也是斜體的

4.1.3 介面

// 形狀
public interface Shape {
    // 獲取尺寸
    int getSize();
}

​ 介面在矩形的第一層中第一行為 <<Interface>>*做介面標識第二行為介面名

4.2 UML類圖表示關係

4.2.1 依賴關係(Dependency)

​ 簡單來說,只要類中用到了另一個類,他們之間就存在了依賴關係(UML類圖中依賴關係用帶虛線的箭頭表示)

設計模式之概述篇
public class UserService {
    // 成員變數
    private UserDao userDao;
    // 方法引數
    public void insert(User user) {}
    // 方法返回值
    public Role getRole(Long id) {
        return null;
    }
    // 方法區域性變數
    public void update() {
        Dept dept = new Dept();
    }
}

類中用到了另一個類包括以上幾種情況:

  1. 類的成員變數
  2. 類中方法的引數
  3. 類中方法的返回值
  4. 類的方法中使用到(區域性變數)

4.2.2 泛化關係(Generalization)

​ 泛化關係實際上就是繼承關係,是依賴關係的特例(在UML類圖中用帶空心三角的實線表示)

public class User extends Person {}

4.2.3 實現關係(Realization / Implementation)

​ 實現關係是指類A實現了介面B,是依賴關係的特例(在UML類圖中用帶空心三角的虛線表示)

public class UserServiceImpl implements IUserService{}

4.2.4 關聯關係(Association)

​ 關聯關係是類與類之間存在的聯絡,是依賴關係的特例(在UML類圖中用帶雙箭頭的實線或者不帶箭頭的雙實線表示雙向關聯,用帶單箭頭的實線表示單向關聯

​ 關聯具有導航性單向關聯雙向關聯

​ 關聯具有多重性一對一多對一多對多

  1. 數字:精確的數量
  2. *或者0..*:表示0到多個
  3. 0..1:表示0或者1個
  4. 1..*:表示1到多個

// 單向一對一關聯
public class Association_01 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份證
    static class IDCard {
    }
}

// 雙向一對一關聯
public class Association_02 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份證
    static class IDCard {
        private Person person;
    }
}

// 單向多對一關聯
public class Association_03 {
    // 人
    static class Person {
        private List<BankCard> bankCardList;
    }
    // 銀行卡
    static class BankCard {
    }
}

// 雙向多對一關聯
public class Association_04 {
    // 人
    static class Person {
        private List<BankCard> bankCardList;
    }
    // 銀行卡
    static class BankCard {
        private Person person;
    }
}

// 多對多關聯
public class Association_05 {
    // 使用者
    static class User {
        private List<Role> roleList;
    }
    // 角色
    static class Role {
        private List<User> userList;
    }
}

4.2.5 聚合關係(Aggregation)

​ 聚合關係是指整體與部分的關係,整體和部分可以分開(比如電腦和滑鼠,滑鼠是電腦的一部分,可以分開),是關聯關係的特例,所以同樣具有導航性多重性(在UML類圖中用空心菱形加實線箭頭表示空心菱形在整體一方箭頭指向部分一方,表示把部分聚合到整體中來)

// 聚合關係
public class Aggregation_01 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份證
    static class IDCard {
    }
}

4.2.6 組合關係(Composition)

​ 組合關係是指整體與部分的關係,整體和部分不可以分開(比如人的身體和頭,頭是身體的一部分,不可以分開),是關聯關係的特例,所以同樣具有導航性多重性(在UML類圖中用實心菱形加實線箭頭表示,實心菱形在整體一方,箭頭指向部分一方,表示把部分組合到整體中來)

// 組合關係
public class Composition_01 {
    // 人
    static class Person {
        private IDCard idCard = new IDCard();
    }
    // 身份證
    static class IDCard {
    }
}

4.3 UML類圖畫圖軟體

​ 我使用的是draw.io(線上版本、PC版都有),支援多種語言

​ 下載連結:https://github.com/jgraph/drawio-desktop/releases

​ 主介面如下:

設計模式之概述篇

5、設計模式的型別

​ 以下羅列了常見的23中設計模式:

建立型模式:

  1. 單例模式
  2. 工廠模式
  3. 抽象工廠模式
  4. 原型模式
  5. 建造者模式

結構型模式:

  1. 介面卡模式
  2. 橋接模式
  3. 裝飾模式
  4. 組合模式
  5. 外觀模式
  6. 享元模式
  7. 代理模式

行為型模式:

  1. 模板方法模式
  2. 命令模式
  3. 訪問者模式
  4. 迭代器模式
  5. 觀察者模式
  6. 中介者模式
  7. 備忘錄模式
  8. 直譯器模式(Interpreter模式)
  9. 狀態模式
  10. 策略模式
  11. 職責鏈模式(責任鏈模式)

6、相關原始碼

​ 本篇章完整程式碼:https://github.com/yushixin-1024/DesignPattern

PS:程式碼不涉及具體業務邏輯,僅僅是為了舉例,方便理解。

相關文章