設計模式——設計模式理念

文章目錄

  • 參考:[設計模式——設計模式理念](https://mp.weixin.qq.com/s/IEduZFF6SaeAthWFFV6zKQ)
  • 參考:[設計模式——工廠方法模式](https://mp.weixin.qq.com/s/7tKIPtjvDxDJm4uFnqGsgQ)
  • 參考:[設計模式——抽象工廠模式](https://mp.weixin.qq.com/s/QRpn41l4RIJnLPr6EysHpw)
  • 參考:[設計模式——模板方法模式](https://mp.weixin.qq.com/s/wbjRs9pFZ_wXa89-y60nrA)
  • 參考:[設計模式——適配器模式](https://mp.weixin.qq.com/s/mznNdNSaJ4K85IA_pMDvYA)
  • 參考:[設計模式——裝飾器模式](https://mp.weixin.qq.com/s/Xb5cc8wJdyW8-byMWvSu5A)
  • 設計模式概念
  • 設計模式的七大原則
    • 1. 單一職責原則(SRP)
      • 思想
      • 示例
    • 2. 接口隔離原則(ISP)
      • 思想
      • 示例
    • 3. 依賴倒轉(置)原則(DIP)
      • 思想
      • 示例
    • 4. 里氏替換原則(LSP)
      • 思想
      • 示例
    • 5. 開閉原則(OCP)
      • 思想
      • 示例
    • 6. 迪米特法則(LoD)
      • 思想
      • 示例
    • 7. 合成復用原則(CRP)
      • 思想
      • 示例
  • 23 種設計模式

參考:設計模式——設計模式理念

參考:設計模式——工廠方法模式

參考:設計模式——抽象工廠模式

參考:設計模式——模板方法模式

參考:設計模式——適配器模式

參考:設計模式——裝飾器模式

設計模式概念

設計模式(Design Pattern)是前輩們對代碼開發經驗的總結,是解決特定問題的一系列套路。它不是語法規定,而是一套用來提高代碼高內聚、低耦合以及可重用(復用)性、可擴展(維護)性、可讀性、可靠性以及安全性的解決方案。

  • 高內聚:模塊內部功能緊密相關,職責單一(如 策略模式 中每個策略類只負責一種算法);
  • 低耦合:模塊間依賴最小化(如 觀察者模式 解耦發布者和訂閱者);
  • 可重用性:相同功能的代碼可重復使用,避免重復造輪子(如 工廠模式 封裝對象創建邏輯,多處復用);
  • 可讀性:編程規范性,便于其他程序員的閱讀和理解;代碼結構符合通用范式(如 單例模式 明確表示全局唯一實例);
  • 可擴展性:當需要增加新的功能時,非常方便;新增功能時無需修改原有代碼(如 裝飾器模式 動態添加功能);
  • 可靠性:當增加新功能后,對原來的功能沒有影響;減少意外錯誤(如 不可變對象模式 避免狀態被篡改);

設計模式的本質是面向對象設計原則的實際運用,是對類的封裝性、繼承性和多態性以及類的關聯關系和組合關系的充分理解。

設計模式的七大原則

設計模式常用的七大原則(OOP七大原則)有:

  1. 單一職責原則(SRP)
  2. 接口隔離原則(ISP)
  3. 依賴倒轉原則(DIP)
  4. 里氏替換原則(LSP)
  5. 開閉原則(OCP)
  6. 迪米特法則(LoD)
  7. 合成復用原則(CRP)

1. 單一職責原則(SRP)

思想

對類來說的,即一個類應該只負責一項職責;或對方法來說的,保證一個方法盡量做好一件事。如類 A 負責兩個不同職責:職責1,職責2。當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2。

核心思想:高內聚、職責分離

  • 職責的單一性:這里的職責是指類所承擔的功能或任務。例如,在一個電商系統中,OrderService 類負責處理訂單相關的業務邏輯,如創建訂單、查詢訂單等,而不應該同時負責用戶登錄、支付等其他與訂單無關的功能。每個職責都應該是明確的、獨立的,并且能夠被清晰地描述和理解。

  • 高內聚性:單一職責原則有助于實現類的高內聚性。內聚性是指類中各個元素(方法、屬性等)之間的緊密程度。當一個類只負責一項職責時,其內部的方法和屬性都與該職責緊密相關,它們之間的內聚性就高。這樣的類更容易理解、維護和擴展,因為所有相關的功能都被集中在一個地方。

  • 降低耦合度:如果一個類承擔了多個職責,那么這些職責之間可能會存在相互依賴關系,這會導致類與其他類之間的耦合度增加。當其中一個職責發生變化時,可能會影響到其他依賴它的類,從而引發連鎖反應,增加了系統的復雜性和維護成本。而遵循單一職責原則,將不同的職責分離到不同的類中,可以降低類之間的耦合度,使得各個類可以獨立地變化和擴展,互不影響。

    典型應用模式:策略模式、命令模式、外觀模式;

    好處:控制類的粒度大小、將對象解耦、提高其內聚性。

示例

假設有一個 Employee 類,用于處理員工的相關信息和操作。

  • 不遵循單一職責原則,代碼可能如下

    public class Employee {private String name;private int age;private String department;// 保存員工信息到數據庫public void saveToDatabase() {// 數據庫操作代碼}// 生成員工報表public void generateReport() {// 報表生成代碼}// 發送員工郵件public void sendEmail() {// 郵件發送代碼}
    }
    

    在上述代碼中,Employee 類承擔了多個職責,包括保存員工信息到數據庫、生成員工報表和發送員工郵件。這違反了單一職責原則,因為這些職責之間并沒有直接的關聯,而且它們的變化原因也不同。

  • 遵循單一職責原則,可以將這些職責分離到不同的類中

    // 員工信息類,只負責存儲員工的基本信息
    public class EmployeeInfo {private String name;private int age;private String department;// 省略getter和setter方法
    }// 員工數據存儲類,負責將員工信息保存到數據庫
    public class EmployeeDatabaseHandler {public void saveToDatabase(EmployeeInfo employeeInfo) {// 數據庫操作代碼}
    }// 員工報表生成類,負責生成員工報表
    public class EmployeeReportGenerator {public void generateReport(EmployeeInfo employeeInfo) {// 報表生成代碼}
    }// 員工郵件發送類,負責發送員工郵件
    public class EmployeeEmailSender {public void sendEmail(EmployeeInfo employeeInfo) {// 郵件發送代碼}
    }
    

    通過將不同的職責分離到不同的類中,每個類都只負責一項職責,遵循了單一職責原則。這樣的設計使得代碼更加清晰、易于維護和擴展。當需要修改某個職責的實現時,只需要在對應的類中進行修改,而不會影響到其他類。

2. 接口隔離原則(ISP)

思想

用多個專門的接口,而不使用單一的總接口,客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上。即為各個類建立它們需要的專用接口,提高其內聚性。

  • 按隔離原則應當這樣處理:將一個大而全的接口拆分成多個小的、特定的接口。比如類 A 通過接口 Interface1 依賴類B,類 C 通過接口 Interface1 依賴類D,如果接口 Interface1 對于類 A 和類 C 來說不是最小接口,那么類 B 和類 D 也必須去實現他們不需要的方法;所以將接口 Interface1 拆分為獨立的幾個接口,類 A 和類 C 分別與他們需要的接口建立依賴關系。也就是采用接口隔離原則;接口 Interface1 中出現的方法,根據實際情況拆分為多個接口代碼實現。

    典型應用模式:適配器模式;

    與單一職責原則類似,將接口隔離,系統地指定一系列規則。

示例

假設有一個 Animal 接口,它包含了動物的各種行為方法

  • 不遵循接口隔離原則代碼示例,代碼可能如下

    Animal 接口包含了動物的各種行為方法

    interface Animal {void eat();void fly();void swim();
    }
    

    現在有一個 Dog 類實現這個接口:

    public class Dog implements Animal {@Overridepublic void eat() {System.out.println("Dog is eating.");}@Overridepublic void fly() {// 狗不會飛,這個方法沒有實際意義throw new UnsupportedOperationException("Dogs can't fly.");}@Overridejavapublic void swim() {System.out.println("Dog is swimming.");}
    }
    

    在這個例子中,Dog 類實現了 Animal 接口,但 fly 方法對于狗來說是不需要的,這就導致 Dog 類不得不實現一個沒有實際意義的方法,違反了接口隔離原則。

  • 遵循接口隔離原則代碼示例

    Animal 接口拆分成多個小接口:

    public interface Eatable {void eat();
    }public interface Flyable {void fly();
    }public interface Swimmable {void swim();
    }
    

    現在有一個 Dog 類實現它需要的接口, Bird 類實現它需要的接口:

    public class Dog implements Eatable, Swimmable {@Overridepublic void eat() {System.out.println("Dog is eating.");}@Overridepublic void swim() {System.out.println("Dog is swimming.");}
    }public class Bird implements Eatable, Flyable {@Overridepublic void eat() {System.out.println("Bird is eating.");}@Overridepublic void fly() {System.out.println("Bird is flying.");}
    }
    

    通過將大接口拆分成多個小接口,Dog 類只需要實現它實際需要的 EatableSwimmable 接口,避免了實現不必要的方法。同樣,Bird 類只需要實現 EatableFlyable 接口。這樣的設計更加靈活,符合接口隔離原則。

3. 依賴倒轉(置)原則(DIP)

思想

依賴倒轉(倒置)的中心思想是面向接口編程;

依賴倒轉原則包含兩個核心要點

  1. 高層模塊不應該依賴低層模塊,兩者都應該依賴抽象:高層模塊通常是指負責業務邏輯和整體流程控制的模塊,而低層模塊則是實現具體功能的細節模塊。依賴倒轉原則強調,高層模塊不應該直接依賴于低層模塊的具體實現,而是應該依賴于抽象接口或抽象類。同樣,低層模塊也應該依賴于抽象,而不是相互依賴具體的實現。

  2. 抽象不應該依賴細節,細節應該依賴抽象:抽象代表著穩定的、通用的概念和規范,而細節則是具體的實現。該原則要求抽象不應該受到具體實現細節的影響,相反,具體的實現細節應該遵循抽象所定義的規范。

    典型應用模式:依賴注入、工廠模式;

    抽象指的是接口或抽象類,細節就是具體的實現類。使用接口或抽象類的目的是制定好規范,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成。要面向接口編程,不要面向實現編程。

依賴倒轉原則的注意事項和細節:

  1. 低層模塊盡量都要有抽象類或接口,或者兩者都有,程序穩定性更好;
  2. 變量的聲明類型盡量是抽象類或接口,這樣我們的變量引用和實際對象間,就存在一個緩沖層,利于程序擴展和優化;
  3. 繼承時遵循里氏替換原則。

示例

假設有一個簡單的電商系統,其中有一個 OrderService 類(高層模塊)負責處理訂單業務,PaymentService 類(低層模塊)負責處理支付業務。

  • 不遵循依賴倒轉原則,代碼可能如下

    // 具體的支付服務類
    public class PaymentService {public void pay() {System.out.println("使用默認支付方式支付");}
    }// 訂單服務類,直接依賴具體的支付服務,OrderService直接依賴于 PaymentService的具體實現,
    public class OrderService {private PaymentService paymentService;public OrderService() {this.paymentService = new PaymentService();}public void createOrder() {// 處理訂單業務邏輯System.out.println("創建訂單");// 調用支付服務paymentService.pay();}
    }
    

    在這個例子中,OrderService 直接依賴于 PaymentService 的具體實現,當需要添加新的支付方式(如支付寶支付、微信支付)時,就需要修改 PaymentService 類和 OrderService 類,這違反了依賴倒轉原則。

  • 遵循依賴倒轉原則,可以引入一個抽象的支付接口

    // 抽象的支付接口
    public interface Payment {void pay();
    }// 具體的支付服務類,實現支付接口
    public class DefaultPaymentService implements Payment {@Overridepublic void pay() {System.out.println("使用默認支付方式支付");}
    }// 訂單服務類,依賴抽象的支付接口
    public class OrderService {private Payment payment;public OrderService(Payment payment) {this.payment = payment;}public void createOrder() {// 處理訂單業務邏輯System.out.println("創建訂單");// 調用支付服務payment.pay();}
    }
    

    通過引入 Payment 接口,OrderService 類依賴于抽象的 Payment 接口,而不是具體的 PaymentService 類。這樣,當需要添加新的支付方式時,只需要實現 Payment 接口,然后在創建 OrderService 對象時傳入相應的實現類即可,不需要修改 OrderService 類的代碼,提高了系統的可擴展性和可維護性。

4. 里氏替換原則(LSP)

思想

里氏替換原則指出:如果S是T的子類型,那么程序中T類型的對象可以被替換為S類型的對象,而不會對程序的正確性產生任何影響。也就是說,所有引用父類的地方必須能透明地使用其子類的對象,一個可以接受父類對象的地方,也應該能夠接受其子類對象,并且程序的行為不會因為將基類對象替換為子類對象而發生改變。里氏替換原則強調了繼承關系中子類與父類的行為兼容性,確保子類可以無縫替換父類而不引起問題。

更通俗地說:子類必須能夠完全替代其父類,而不影響程序的正確性。

  • 典型應用模式:模板方法模式;

核心要點

  • 子類必須完全實現父類的方法:子類是對父類的擴展和細化,因此子類應該實現父類中定義的所有抽象方法和非抽象方法。如果子類沒有實現父類的某些方法,那么在使用子類對象替換父類對象時,就可能會導致程序出現錯誤或異常。
    • 實現抽象類或接口的基本要求;
    • 子類可以覆蓋父類的非抽象方法,但覆蓋時需保證不改變父類方法的預期行為,確保使用子類對象替換父類對象時程序的正確性;
  • 子類中可以增加自己特有的方法:在滿足里氏替換原則的前提下,子類可以添加自己特有的方法和屬性,以實現更具體的功能。但這些新增的特性不能影響到子類與父類之間的替換關系,即不能因為子類的特殊行為而破壞了程序中依賴父類的部分的正常運行。
    • 這些新增的特性不會影響子類與父類之間的替換關系,因為在使用父類引用指向子類對象時,不會調用到子類特有的方法,只有當進行類型轉換后才能使用這些特有的方法;
  • 覆蓋或實現父類的方法時輸入參數可以被放大:里氏替換原則允許子類在覆蓋或實現父類方法時,將方法的輸入參數類型放寬。這意味著子類方法可以接受更廣泛的輸入參數,而不會影響到使用父類對象的代碼。
    • 子類方法的參數類型可以是父類方法參數類型的父類型(即更寬泛的類型);(如父類用Integer,子類可以用Number
    • 子類方法可以接受比父類更寬松的參數值范圍;(如父類約束入參>0,子類可以放開入參約束>=0)
  • 覆蓋或實現父類的方法時輸出參數可以被縮小:與輸入參數相反,子類在覆蓋或實現父類方法時,輸出參數的類型應該是父類方法輸出參數類型的子類型。這是因為調用者在使用父類對象時,期望得到的是父類方法所聲明的返回類型或其子類型的對象。如果子類方法返回的是父類返回類型的超類型對象,那么可能會導致調用者在處理返回結果時出現錯誤。
    • 子類方法返回類型可以是父類方法返回類型的子類型(父類返回Number,子類可以返回Integer);
    • 子類方法可以承諾比父類更精確的返回值特性(父類返回任意集合,子類返回排序集合);
    • 子類方法可以拋出比父類更少的異常或更具體的異常類型;

示例

1、子類必須完全實現父類的方法

子類要實現父類中定義的所有抽象方法和非抽象方法。若子類未實現父類的某些方法,使用子類對象替換父類對象時,程序可能出錯。

  • 符合里氏替換原則的示例代碼

    // 抽象父類:交通工具
    abstract class Vehicle {// 抽象方法:啟動public abstract void start();
    }// 子類:汽車
    class Car extends Vehicle {@Overridepublic void start() {System.out.println("汽車啟動");}
    }// 子類:自行車
    class Bicycle extends Vehicle {@Overridepublic void start() {System.out.println("自行車蹬起來啟動");}
    }// 測試類
    public class LSPExample1 {public static void main(String[] args) {Vehicle car = new Car();Vehicle bicycle = new Bicycle();startVehicle(car);startVehicle(bicycle);}public static void startVehicle(Vehicle vehicle) {vehicle.start();}
    }
    

    Vehicle 是抽象父類,定義了抽象方法 startCarBicycle 子類都實現了該方法。在 startVehicle 方法中,可傳入 CarBicycle 對象,程序正常運行。

  • 不符合里氏替換原則的示例代碼

    // 抽象父類:交通工具
    abstract class Vehicle {// 抽象方法:啟動public abstract void start();
    }// 子類:汽車
    class Car extends Vehicle {// 未實現 start 方法
    }// 測試類
    public class LSPViolationExample1 {public static void main(String[] args) {Vehicle car = new Car();startVehicle(car);}public static void startVehicle(Vehicle vehicle) {vehicle.start(); // 編譯錯誤,Car 類未實現 start 方法}
    }
    

    Car 子類沒有實現父類 Vehiclestart 方法,當調用 startVehicle 方法時,會出現編譯錯誤,無法正常使用子類對象替換父類對象。

2、子類中可以增加自己特有的方法

在滿足里氏替換原則的基礎上,子類可添加自身特有的方法和屬性,但不能影響子類與父類的替換關系。

  • 符合里氏替換原則的示例代碼

    // 父類:動物
    class Animal {public void eat() {System.out.println("動物進食");}
    }// 子類:貓
    class Cat extends Animal {public void meow() {System.out.println("喵喵叫");}
    }// 測試類
    public class LSPExample2 {public static void main(String[] args) {Animal cat = new Cat();cat.eat();if (cat instanceof Cat) {Cat realCat = (Cat) cat;realCat.meow();}}
    }
    

    Cat 類繼承自 Animal 類,添加了 meow 方法。可將 Cat 對象賦值給 Animal 類型變量并調用 eat 方法,若要調用 meow 方法,需進行類型轉換。

  • 不符合里氏替換原則的示例代碼

    // 父類:動物
    class Animal {public void eat() {System.out.println("動物進食");}
    }// 子類:貓
    class Cat extends Animal {public void meow() {System.out.println("喵喵叫");}@Overridepublic void eat() {throw new UnsupportedOperationException("貓拒絕進食");}
    }// 測試類
    public class LSPViolationExample2 {public static void main(String[] args) {Animal cat = new Cat();try {cat.eat(); // 調用時拋出異常,破壞了原有行為} catch (UnsupportedOperationException e) {System.out.println("出現異常:" + e.getMessage());}}
    }
    

    Cat 類重寫 eat 方法時拋出異常,改變了父類方法的正常行為。當使用 Cat 對象替換 Animal 對象調用 eat 方法時,程序出現異常,破壞了程序的正確性。

3、覆蓋或實現父類的方法時輸入參數可以被放大

子類在覆蓋或實現父類方法時,可放寬方法的輸入參數類型,使子類方法能接受更廣泛的輸入參數,且不影響使用父類對象的代碼。

  • 符合里氏替換原則的示例代碼

    import java.util.ArrayList;
    import java.util.List;// 父類
    class Parent {public void printList(List<Integer> list) {for (Integer num : list) {System.out.println(num);}}
    }// 子類
    class Child extends Parent {public void printList(List<Number> list) {for (Number num : list) {System.out.println(num);}}
    }// 測試類
    public class LSPExample3 {public static void main(String[] args) {Parent parent = new Parent();Parent child = new Child();List<Integer> intList = new ArrayList<>();intList.add(1);intList.add(2);parent.printList(intList);child.printList(intList);}
    }
    

    父類 ParentprintList 方法接受 List<Integer> 類型參數,子類 ChildprintList 方法接受 List<Number> 類型參數。由于 IntegerNumber 的子類,Child 對象可正常處理 List<Integer> 類型參數。

  • 不符合里氏替換原則的示例代碼

    import java.util.ArrayList;
    import java.util.List;// 父類
    class Parent {public void printList(List<Number> list) {for (Number num : list) {System.out.println(num);}}
    }// 子類
    class Child extends Parent {public void printList(List<Integer> list) {for (Integer num : list) {System.out.println(num);}}
    }// 測試類
    public class LSPViolationExample3 {public static void main(String[] args) {Parent parent = new Parent();Parent child = new Child();List<Number> numberList = new ArrayList<>();numberList.add(1.0);numberList.add(2.0);parent.printList(numberList);// child.printList(numberList); 編譯錯誤,Child 類的 printList 方法不能接受 List<Number> 類型參數}
    }
    

    子類 ChildprintList 方法輸入參數類型范圍比父類小,當使用 Child 對象替換 Parent 對象處理 List<Number> 類型參數時,會出現編譯錯誤。

4、覆蓋或實現父類的方法時輸出參數可以被縮小

子類在覆蓋或實現父類方法時,輸出參數的類型應是父類方法輸出參數類型的子類型。調用者使用父類對象時,期望得到父類方法聲明的返回類型或其子類型的對象。

  • 符合里氏替換原則的示例代碼

    // 父類
    class SuperClass {public Number getNumber() {return 1;}
    }// 子類
    class SubClass extends SuperClass {@Overridepublic Integer getNumber() {return 2;}
    }// 測試類
    public class LSPExample4 {public static void main(String[] args) {SuperClass superClass = new SuperClass();SuperClass subClass = new SubClass();Number num1 = superClass.getNumber();Number num2 = subClass.getNumber();System.out.println(num1);System.out.println(num2);}
    }
    

    父類 SuperClassgetNumber 方法返回 Number 類型,子類 SubClassgetNumber 方法返回 Integer 類型,IntegerNumber 的子類。SubClass 對象可正常賦值給 SuperClass 類型變量并調用 getNumber 方法。

  • 不符合里氏替換原則的示例代碼

    // 父類
    class SuperClass {public Integer getNumber() {return 1;}
    }// 子類
    class SubClass extends SuperClass {@Overridepublic Number getNumber() {return 2.0;}
    }// 測試類
    public class LSPViolationExample4 {public static void main(String[] args) {SuperClass superClass = new SuperClass();SuperClass subClass = new SubClass();Integer num1 = superClass.getNumber();// Integer num2 = subClass.getNumber(); 編譯錯誤,無法將 Number 類型賦值給 Integer 類型}
    }
    

    子類 SubClassgetNumber 方法返回類型是 Number,比父類的返回類型范圍大。當使用 SubClass 對象替換 SuperClass 對象時,將返回值賦值給 Integer 類型變量會出現編譯錯誤。

5. 開閉原則(OCP)

思想

對擴展開放,對修改關閉;

解釋:擴展原來程序,但盡量不修改原來的程序,即通過擴展(而非修改)增加新功能;

  • 核心思想:通過抽象和繼承實現擴展性。開閉原則的核心在于通過抽象和封裝,將軟件系統中相對穩定的部分和容易變化的部分分離。穩定的部分作為抽象層,定義了系統的基本結構和行為規范;容易變化的部分則通過具體的實現類來體現,當需求發生變化時,只需要添加新的實現類,而不需要修改抽象層和其他已有的實現類。

    典型應用模式:裝飾器模式、適配器模式、策略模式、模板方法模式;

示例

以一個簡單的圖形繪制為例,說明開閉原則的應用。

  • 不遵循開閉原則的設計,代碼可能如下

    // 圖形類
    class Shape {String type;public Shape(String type) {this.type = type;}
    }// 圖形繪制類
    class Drawing {public void drawShape(Shape shape) {if ("circle".equals(shape.type)) {drawCircle();} else if ("rectangle".equals(shape.type)) {drawRectangle();}}private void drawCircle() {System.out.println("繪制圓形");}private void drawRectangle() {System.out.println("繪制矩形");}
    }
    

    在這個設計中,如果需要添加新的圖形(如三角形),就需要修改 Drawing 類的 drawShape 方法,添加新的 if-else 分支,這違反了開閉原則。

  • 遵循開閉原則的設計

    // 抽象圖形類
    abstract class Shape {public abstract void draw();
    }// 圓形類
    class Circle extends Shape {@Overridepublic void draw() {System.out.println("繪制圓形");}
    }// 矩形類
    class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("繪制矩形");}
    }// 圖形繪制類
    class Drawing {public void drawShape(Shape shape) {shape.draw();}
    }
    

    在這個設計中,Shape 是抽象類,定義了抽象方法 drawCircleRectangle 是具體的圖形類,實現了 draw 方法。Drawing 類的 drawShape 方法通過調用 Shape 對象的 draw 方法來繪制圖形。當需要添加新的圖形(如三角形)時,只需要創建一個新的類繼承自 Shape,并實現 draw 方法,而不需要修改 Drawing 類的代碼,符合開閉原則。

6. 迪米特法則(LoD)

思想

一個對象應盡可能少地了解其他對象,具體來說,一個類對于其他類知道得越少越好,盡量降低類與類之間的耦合;一個類應該只和它的直接朋友通信,而避免和陌生的類直接通信(不要和"陌生人"說話、不要直接操作"朋友的朋友"、不要暴露內部結構給外部)

"直接朋友"包括:

  • 當前對象本身(this):對象自身的屬性和方法可以直接訪問。
  • 以參數形式傳入到當前對象方法中的對象:在方法內部可以直接使用這些參數對象。
  • 當前對象的成員變量(屬性):如果當前對象包含其他對象作為成員變量,那么這些成員對象也是朋友;
  • 如果當前對象的成員對象是一個集合,那么集合中的元素也都是朋友。
  • 當前對象的方法所創建或實例化的對象:通過 new 關鍵字創建的對象,可在當前對象中直接使用。

典型應用模式:外觀模式、中介者模式;

示例

假設有一個學校管理系統,包含 School 類、Teacher 類和 Student 類。School 類需要統計所有學生的數量。

  • 不遵循迪米特法則的設計,代碼可能如下

    // 學生類
    class Student {// 學生相關屬性和方法
    }// 教師類
    class Teacher {private Student[] students;public Teacher(Student[] students) {this.students = students;}public Student[] getStudents() {return students;}
    }// 學校類
    class School {private Teacher[] teachers;public School(Teacher[] teachers) {this.teachers = teachers;}public int getTotalStudents() {int total = 0;for (Teacher teacher : teachers) {Student[] students = teacher.getStudents();total += students.length;}return total;}
    }
    

    在這個設計中,School 類通過 Teacher 類獲取了 Student 類的信息,這使得 School 類與 Student 類之間產生了不必要的交互,違反了迪米特法則。School 類知道了太多關于 Student 類的信息,增加了類之間的耦合度。

  • 遵循迪米特法則的設計

    // 學生類
    class Student {// 學生相關屬性和方法
    }// 教師類
    class Teacher {private Student[] students;public Teacher(Student[] students) {this.students = students;}public int getStudentCount() {return students.length;}
    }// 學校類
    class School {private Teacher[] teachers;public School(Teacher[] teachers) {this.teachers = teachers;}public int getTotalStudents() {int total = 0;for (Teacher teacher : teachers) {total += teacher.getStudentCount();}return total;}
    }
    

    在這個設計中,School 類只與 Teacher 類進行交互,通過調用 Teacher 類的 getStudentCount 方法來獲取學生數量,而不需要了解 Student 類的具體信息。這樣,School 類對其他類的了解最少,遵循了迪米特法則,降低了類之間的耦合度。

7. 合成復用原則(CRP)

思想

優先使用對象組合或者聚合等關聯關系,其次才考慮使用繼承關系來達到復用目的。簡單來說,就是在一個新的對象里通過關聯關系(組合、聚合)來使用一些已有的對象,使之成為新對象的一部分;新對象通過委派調用已有對象的方法達到復用功能的目的,而不是通過繼承父類來獲得已有的功能。

典型應用模式:裝飾器模式、橋接模式;

組合與聚合

  • 組合:是一種強 “擁有” 關系,體現了嚴格的部分和整體的關系,部分和整體的生命周期是一致的。例如,汽車和發動機的關系,發動機是汽車的一部分,沒有汽車,發動機通常沒有獨立的意義,并且發動機的生命周期和汽車的生命周期緊密相關。
  • 聚合:是一種弱 “擁有” 關系,體現的是 A 對象可以包含 B 對象,但 B 對象不是 A 對象的一部分。比如,公司和員工的關系,員工是公司的一部分,但員工可以獨立于公司存在,有自己獨立的生命周期。

示例

假設要設計一個學生課程管理系統。

  • 不遵循合成復用原則(使用繼承來實現復用),代碼可能如下

    // 課程類
    class Course {private String courseName;private String teacher;public Course(String courseName, String teacher) {this.courseName = courseName;this.teacher = teacher;}public void showCourseInfo() {System.out.println("課程名: " + courseName + ", 授課教師: " + teacher);}
    }// 學生選課類,繼承自課程類
    class StudentCourse extends Course {private String studentName;public StudentCourse(String courseName, String teacher, String studentName) {super(courseName, teacher);this.studentName = studentName;}public void showStudentInfo() {System.out.println("選課學生: " + studentName);}
    }
    

    在這個設計中,StudentCourse 類繼承了 Course 類。然而,繼承是一種強耦合關系。要是 Course 類發生改變,例如添加或修改方法,可能會對 StudentCourse 類產生影響。而且,從邏輯上來說,學生選課并非是課程的一種特殊形式,這種繼承關系在語義上不太合適。

  • 遵循合成復用原則(使用組合)

    // 課程類
    class Course {private String courseName;private String teacher;public Course(String courseName, String teacher) {this.courseName = courseName;this.teacher = teacher;}public void showCourseInfo() {System.out.println("課程名: " + courseName + ", 授課教師: " + teacher);}
    }// 學生類
    class Student {private String studentName;private Course[] selectedCourses;public Student(String studentName, Course[] selectedCourses) {this.studentName = studentName;this.selectedCourses = selectedCourses;}public void showStudentAndCourses() {System.out.println("學生姓名: " + studentName);for (Course course : selectedCourses) {course.showCourseInfo();}}
    }
    

    在這個設計里,Student 類通過組合的方式持有 Course 對象的引用。Student 類和 Course 類是松耦合關系,當 Course 類的實現發生變化時,只要其接口(如 showCourseInfo 方法)保持不變,就不會對 Student 類產生影響。同時,這種設計更符合實際邏輯,學生可以選擇多門課程,并且能靈活地對課程進行管理。

23 種設計模式

23中設計模式:(GoF23)

  • 創建型模式:(5種)跟創建對象有關

    單例模式、工廠模式、抽象工廠模式、建造者模式、原型模式;

  • 結構型模式:(7種)

    適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式;

  • 行為型模式:(11種)

    模板方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器
    模式、狀態模式、策略模式、責任鏈模式、訪問者模式;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/74085.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/74085.shtml
英文地址,請注明出處:http://en.pswp.cn/web/74085.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android 16開發實戰指南|鎖屏交互+Vulkan優化全解析

一、環境搭建與項目初始化 1. 安裝Android Studio Ladybug 下載地址:Android Studio官網關鍵配置: # 安裝后立即更新SDK SDK Manager → SDK Platforms → 安裝Android 16 (Preview) SDK Manager → SDK Tools → 更新Android SDK Build-Tools至34.0.0 # 通過命令行安裝SDK組…

selenium應用測試場景

Selenium 是主流的 Web 自動化測試框架&#xff0c;主要用于基于瀏覽器的 Web 應用測試。以下是 Selenium 的典型測試場景和適用場景&#xff0c;以及與 Appium 的對比&#xff1a; 1. Selenium 的核心測試場景 (1) Web 功能測試&#xff08;Functional Testing&#xff09; 表…

[Vue]生命周期

在編程領域生命周期指的即一個對象從創建到銷毀的過程。 Vue的生命周期大概分為四個階段&#xff1a; 創建階段 在該階段&#xff0c;vue的主要工作是為渲染模板做準備工作。比如處理data中的數據&#xff0c;使其變為響應式數據。在html中普通的數據往往不具備響應式等一系列…

低代碼平臺,智慧城市建設的加速器

隨著城市數字化進程加速&#xff0c;智慧停車、智慧交通、城市數據治理等領域對技術敏捷性和開發效率的需求日益凸顯。低代碼平臺憑借其可視化開發、模塊化設計和快速部署能力&#xff0c;正在成為推動城市治理智能化升級的核心工具。本文將通過低代碼在智慧城市建設上應用的展…

14 配置Hadoop集群-配置歷史和日志服務

第一課時 一、導入 前面的課程我們搭建了hadoop集群&#xff0c;并成功啟動了它&#xff0c;接下來我們看看如何去使用集群。 測試的內容包括&#xff1a;1.上傳文件&#xff0c;2.下載文件&#xff0c;3.運行程序 二、授新 &#xff08;一&#xff09;配置運行任務的歷史服務器…

0102-web架構網站搭建-基礎入門-網絡安全

文章目錄 1. 常規2 站庫分離3 前后端分離4 集成環境5 docker6 分配站結語 1. 常規 結構&#xff1a;源碼數據都在同服務器 影響&#xff1a;無&#xff0c;常規安全測試手法 2 站庫分離 結構&#xff1a;源碼和數據庫不在同服務器 存儲&#xff1a;其他服務器上數據庫或者…

【分布式系統】-2-GFS

MIT的【分布式系統課程】學習記錄 內容純屬個人學習過程中的筆記記錄&#xff0c;如果有侵權現象請留言&#xff0c;會立刻刪除 分布式存儲系統的難點&#xff1a; 設計大型存儲系統的出發點&#xff1a;利用數百臺計算機資源同時完成大量工作&#xff0c;達到性能加成 如何做…

黑盒測試的場景法(能對項目業務進行設計測試點)

定義: 通過運用場景來對系統的功能點或業務流程的描述&#xff0c;設計用例遍歷場景&#xff0c;驗證軟件系統功能的正確性從而提高測試效果的一種方法。 場景法一般包含基本流和備用流。 基本流:軟件功能的正確流程&#xff0c;通常一個業務只存在一個基本流且基本流有一個…

22 安裝第三方包

一、什么是第三方包 在 Python 的世界里&#xff0c;包就像是一個個功能強大的工具箱&#xff0c;它將多個 Python 模塊收納其中&#xff0c;而每個模塊又蘊含著豐富多樣的具體功能。可以說&#xff0c;一個包就是一系列同類功能的集合體&#xff0c;它們就像緊密協作的團隊&a…

MyBatisPlus不等于如何使用

在 MyBatis Plus 中&#xff0c;ne 方法用于構建不等于條件的 SQL 查詢。以下是 ne 方法的詳細用法&#xff1a; 基本用法 ne 方法可以用于 QueryWrapper 或 LambdaQueryWrapper 中&#xff0c;用于指定某個字段的值不等于指定的值。它對應于 SQL 中的 ! 或 <> 操作符。 …

[學術][人工智能] 001_什么是神經網絡?

神經網絡是一種模擬生物神經系統的計算模型&#xff0c;具有廣泛的應用和重要的研究價值。以下將從不同方面詳細介紹神經網絡。 一、神經網絡的發展歷程 20 世紀 60 年代&#xff0c;Hubel 和 Wiesel 在研究貓腦皮層中發現了用于局部敏感和方向選擇的神經元結構&#xff0c;卷積…

Unity3D仿星露谷物語開發32之地面屬性決定角色動作

1、目標 根據地面屬性&#xff08;diggable, canDropItem, canPlaceFurniture, isPath, isNPCObstacle&#xff09;決定角色進行何種操作。比如沒有canDropItem屬性的地面&#xff0c;則不能放置物體。 2、優化保存Item數據 PS&#xff1a;這個是對已有代碼的優化&#xff0…

031-valgrind

valgrind 以下是從原理到代碼實現、參數優化及結果分析的Valgrind技術調研報告&#xff0c;結合C示例和可視化工具使用說明&#xff1a; 一、Valgrind核心原理與架構 1.1 系統架構 #mermaid-svg-xIgV3fg90dGhEEq4 {font-family:"trebuchet ms",verdana,arial,sans-…

C/C++藍橋杯算法真題打卡(Day12)

一、P8752 [藍橋杯 2021 省 B2] 特殊年份 - 洛谷 算法代碼&#xff1a; #include<bits/stdc.h> using namespace std; int cnt;int main() {int i1;while(i<5){int num;cin>>num;string string_numto_string(num);if(string_num[0]string_num[2]&&str…

MySQL 復制與主從架構(Master-Slave)

MySQL 復制與主從架構&#xff08;Master-Slave&#xff09; MySQL 復制與主從架構是數據庫高可用和負載均衡的重要手段。通過復制數據到多個從服務器&#xff0c;既可以實現數據冗余備份&#xff0c;又能分擔查詢壓力&#xff0c;提升系統整體性能與容錯能力。本文將詳細介紹…

《大模型部署》——ollama下載及大模型本地部署(詳細快速部署)

ollama Ollama 是一款開源跨平臺的大語言模型&#xff08;LLM&#xff09;運行工具&#xff0c;旨在簡化本地部署和管理 AI 模型的流程。 下載ollama 進入官網下載https://ollama.com/ 選擇需要的系統下載 下載完成后直接進行安裝 下載大模型 選擇想要部署的模型&#…

Java關于包和final

什么是包&#xff1f; 包就是文件夾。用來管理等各種不同功能的java類&#xff0c;方便后期代碼維護 包名的規則&#xff1a;公司域名反寫包的作用&#xff0c;需要全部英文小寫&#xff0c;見名知意。例如&#xff1a;com.pdsu.domain package com.pdsu.demain;public class…

數據層的基本操作

1.數據層的更名: Arcmap內容列表中,數據框所包含的每個圖層及每個圖層所包含的一系列地理要素,都有相應的描述字符與之對應.默認情況下,添加進地圖的圖層是以其數據源的名字命名的,而地理要素的描述就是要素類型字段取值.由于這些命名影響到用戶對數據的理解和地圖輸出時的圖例…

Vue舞臺劇

Vue 的舞臺劇比喻 &#x1f3ad;&#xff08;完整表格版&#xff09; Vue 結構及路由管理 Vue 結構比喻&#xff08;舞臺劇&#xff09;&#x1f3ad;作用index.html空白場地 &#x1f3df;?Vue 負責填充內容&#xff0c;提供 #app 容器&#xff0c;等待演出開始。main.js導…

【Windows】win10系統安裝.NET Framework 3.5(包括.NET 2.0和3.0)失敗 錯誤代碼:0×80240438

一、.NET3.5(包括.NET 2.0和3.0)安裝方式 1.1 聯網安裝(需要聯網,能訪問微軟,簡單,很可能會失敗) 1.2 離線安裝-救急用(需要操作系統iso鏡像文件,復雜,成功幾率大) 二、聯網安裝 通過【控制面板】→【程序】→【程序和功能】→【啟用或關閉Windows功能】 下載過程…