設計模式之概述篇

来源:https://www.cnblogs.com/yushixin1024/archive/2022/06/30/16427453.html
-Advertisement-
Play Games

設計模式,設計模式概述,UML,UML類圖,七大原則,程式設計七大原則 ...


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:代碼不涉及具體業務邏輯,僅僅是為了舉例,方便理解。

本文來自博客園,作者:颯沓流星,轉載請註明原文鏈接:https://www.cnblogs.com/yushixin1024/p/16427453.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Javascript趣味程式從需求到實現-體重測量器 Version 0.0.1 一、需求梳理 二、分析結果: 1、需要提供輸入身高、體重、以及選擇性別的HTML組件,如input、select 2、需要對信息進行驗證,專門的工具類完成這個職責如 “BmiUtils” 3、無效的信息需要提示 4、與 ...
  • 一、效果圖 二、實現邏輯 將需要動態添加的表單項項的綁定值存為一個數組 以迴圈的方式展示form表單 點擊`+`按鈕觸發事件,向數組中新加一個item 點擊`-`按鈕觸發事件,根據迴圈的得到的index來刪除數組中相對應位置的item 三、代碼實現 <template> <div> <el-butt ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 JS是一門單線程語言,單線程就意味著,所有的任務需要排隊,前一個任務結束,才會執行下一個任務。這樣所導致的問題是:如果JS執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染載入阻塞的覺。為瞭解決這個問題,JS中出現了同步和異 ...
  • 一、效果圖 二、代碼實現 註:需要安裝依賴 pnpm i sortablejs -S <template> <div style="padding: 15px"> <h3 style="text-align: left; padding: 10px; background: #ccc"> vue+e ...
  • 1. Text 的絕對居中 Android中顯示時會有預設的padding保留,導致垂直居中會有誤差 //不寫下麵兩個,Android系統上文字會偏下* includeFontPadding: false, textAlignVertical: "center", Text省略號顯示 <Text n ...
  • 2.兩數相加 給你兩個 非空 的鏈表,表示兩個非負的整數。它們每位數字都是按照 逆序 的方式存儲的,並且每個節點只能存儲 一位 數字。 請你將兩個數相加,並以相同形式返回一個表示和的鏈表。 你可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。 示例 1: 輸入:l1 = [2,4,3], l2 ...
  • 編寫代碼中通常會有快速初始化數組的需求,例如我們需要一個類似matlab里的zeros函數,假如這裡我們需要生成一個0-23的數組用於表示一天24小時。 最基本的做法如下: function(){ let hours = []; for(let k = 0; k < 24; k++ )hours.p ...
  • 視頻鏈接:P8~P29 黑馬程式員pink老師前端入門教程,零基礎必看的h5(html5)+css3+移動 參考鏈接: HTML 元素 1.HTML語法規範 1.1 基本語法概述 HTML 標簽是由尖括弧包圍的關鍵詞,例如<html>。 HTML 標簽 通常成對出現,例如 開始標簽 和 結束標簽 , ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...