悉數六大設計原則
目錄
- 悉數六大設計原則
- 前言?
- 誰發明了設計模式
- 設計原則
- 設計原則與設計模式的關系
- 單一職責
- 什么是單一職責
- 不遵循單一職責原則的設計
- 遵循單一職責原則的設計
- 單一職責的優點
- 示例代碼:
- 里氏替換原則
- 什么是里氏替換原則
- 示例代碼:
- 違反里氏替換原則的代碼
- 遵循里氏替換原則的代碼
- 里氏替換原則的優點
- 依賴倒置原則
- 什么是依賴倒置原則
- 依賴倒置原則的核心思想
- 依賴倒置原則的優點
- 示例代碼:
- 不遵循依賴倒置原則的設計
- 遵循依賴倒置原則的設計
- 實際應用中的優點
- 接口隔離原則
- 什么是接口隔離原則
- 示例代碼:
- 不遵循接口隔離原則的設計
- 遵循接口隔離原則的設計
- 實際應用中的好處
- 示例代碼:
- 迪米特原則
- 什么是迪米特法則
- 迪米特法則的規則
- 示例代碼:
- 違反迪米特法則的代碼
- 遵循迪米特法則的代碼
- 迪米特法則的優點
- 開閉原則
- 什么是開閉原則
- 如何實現開閉原則
- 示例代碼:
- 場景描述
- 違反開閉原則的設計
- 遵循開閉原則的設計
- 開閉原則的優點
- 總結🍭
前言?
大家好,我是Leo哥🫣🫣🫣,今天開始我們來學習一下關于設計模式的內容。提起設計模式,大家肯定不陌生,可能在此之前你也多少了了解過設計模式,但在實際的業務開發中使?用卻不不多,多數時候都是?大?面積堆積ifelse 組裝業務流程,對于?一次次的需求迭代和邏輯補充,只能東拼?西湊 Ctrl+C 、 Ctrl+V 。作為一名優秀的程序員,設計模式可謂是必修課,接下來就跟著Leo哥一起來了解了解設計模式吧。
誰發明了設計模式
設計模式的概念最早是由 克?里里斯托佛·亞歷?山?大 在其著作 《建筑模式語?言》 中?首次提出的。 本書介紹了了城市設計的 語?言,提供了了253個描述城鎮、鄰?里里、住宅、花園、房間及?西部構造的模式, ?而此類 語?言 的基本單元就是模式。后來, 埃?里里希·伽瑪 、 約翰·弗利利賽德斯 、 拉爾夫·約翰遜 和 理理查德·赫爾姆 這四位作者接受了了模式的概念。 1994 年年, 他們出版了了 《設計模式: 可復?用?面向對象軟件的基礎》 ?一書, 將設計模式的概念應?用到程序開發領域中。
設計原則
在學習設計模式之前,我們應該先了解一下設計原則,那么什么是設計原則呢。設計原則是指導軟件設計的一系列準則和規范,旨在幫助開發人員創建高質量的代碼。這些原則強調代碼的可維護性、可擴展性和靈活性,減少系統的復雜性和提高代碼的可理解性。
設計原則與設計模式的關系
- 設計原則:設計原則是高層次的指導方針,提供了軟件設計的基本框架和標準。這些原則可以應用于任何軟件開發項目,以確保代碼的高質量和長期可維護性。
- 設計模式:設計模式是針對特定問題的具體解決方案,是對設計原則的具體應用和實現。設計模式提供了可以復用的代碼結構和模板,幫助開發人員解決常見的設計問題。
話不多說,下面我們首先來學習一下經典的六大設計原則吧。
單一職責
首先, 我們來看單一職責的定義。
單一職責原則,全稱Single Responsibility Principle, 簡稱SRP. A class should have only one reason to change 類發生更改的原因應該只有一個 。
什么是單一職責
單一職責原則(Single Responsibility Principle, SRP) 是軟件設計中的一種原則,它強調每個類應該只有一個職責,即一個類只負責一項功能或一類功能的邏輯。這個原則是 SOLID 原則中的第一個,它有助于提高代碼的可維護性、可讀性和可擴展性。
就一個類而言,應該僅有一個引起它變化的原因。應該只有一個職責。如果一個類有一個以上的職責,這些職責就耦合在了一起。一個職責的變化可能會削弱或者抑制這個類完成其他職責的能力。這會導致脆弱的設計。當一個職責發生變化時,可能會影響其它的職責。另外,多個職責耦合在一起,會影響復用性。想要避免這種現象的發生,就要盡可能的遵守單一職責原則。
單一職責原則的核心就是解耦和增強內聚性。
不遵循單一職責原則的設計
public class ReportManager {public String generateReport() {// 生成報告的邏輯return "Report Content";}public void printReport(String report) {// 打印報告的邏輯System.out.println(report);}
}
在上面的代碼示例中,ReportManager
類同時負責生成報告和打印報告。這兩個職責耦合在一起,如果將來需要修改打印報告的方式,我們需要修改 ReportManager
類,這違反了單一職責原則。
遵循單一職責原則的設計
我們可以將生成報告和打印報告的職責分離到不同的類中:
// 生成報告的類
public class ReportGenerator {public String generateReport() {// 生成報告的邏輯return "Report Content";}
}// 打印報告的類
public class ReportPrinter {public void printReport(String report) {// 打印報告的邏輯System.out.println(report);}
}
現在,ReportGenerator
類只負責生成報告,ReportPrinter
類只負責打印報告。這種設計使得每個類的職責單一,如果將來需要修改打印報告的方式,只需要修改 ReportPrinter
類,不會影響到 ReportGenerator
類。
單一職責的優點
- 提高可維護性:職責單一的類更容易理解和維護。每個類的代碼量減少,邏輯更加清晰。
- 提高可復用性:職責單一的類可以更容易地在不同的上下文中復用,而無需擔心未使用的功能帶來的負擔。
- 增強測試性:職責單一的類通常具有較少的依賴,單元測試更容易編寫和執行。
- 降低耦合度:將不同的職責分離到不同的類中,減少了類之間的耦合,增強了系統的靈活性和可擴展性。
示例代碼:
下面我們來寫一個一個更完整的代碼示例,展示了如何使用單一職責原則設計一個簡單的學生管理系統:
// 學生類,負責學生信息
public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}
}// 學生數據庫操作類,負責與數據庫的交互
public class StudentRepository {public void save(Student student) {// 將學生信息保存到數據庫的邏輯System.out.println("Saving student: " + student.getName());}public Student findByName(String name) {// 從數據庫中查找學生信息的邏輯return new Student(name, 20); // 模擬返回一個學生對象}
}// 學生信息展示類,負責學生信息的展示
public class StudentView {public void displayStudentInfo(Student student) {System.out.println("Student Name: " + student.getName());System.out.println("Student Age: " + student.getAge());}
}// 主類,負責調用其他類完成具體功能
public class Main {public static void main(String[] args) {StudentRepository studentRepository = new StudentRepository();StudentView studentView = new StudentView();Student student = new Student("John Doe", 20);studentRepository.save(student);Student retrievedStudent = studentRepository.findByName("John Doe");studentView.displayStudentInfo(retrievedStudent);}
}
Student
類只負責保存學生的基本信息。StudentRepository
類負責與數據庫的交互,處理學生信息的保存和查詢。StudentView
類負責展示學生信息。
里氏替換原則
什么是里氏替換原則
里氏替換原則(Liskov Substitution Principle,LSP)是由計算機科學家 Barbara Liskov 在 1987 年提出的,是面向對象設計的五大基本原則之一(SOLID 原則中的 L)。里氏替換原則的核心思想是:在一個程序中,如果基類可以被子類替換,而不影響程序的正確性,那么這個子類是正確的。換句話說,子類對象應該能夠替換基類對象而不改變程序的行為。
里式替換原則是用來幫助我們在繼承關系中進行父子類的設計。
里氏替換原則(Liskov Substitution principle)是對子類型的特別定義的. 為什么叫里式替換原則呢?因為這項原則最早是在1988年,由麻省理工學院的一位姓里的女士(Barbara Liskov)提出來的。
里氏替換原則主要闡述了有關繼承的一些原則,也就是什么時候應該使用繼承,什么時候不應該使用繼承,以及其中蘊含的原理。里氏替換原是繼承復用的基礎,它反映了基類與子類之間的關系,是對開閉原則的補充,是對實現抽象化的具體步驟的規范。
里式替換原則有兩層定義:
定義1
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。
如果S是T的子類,則T的對象可以替換為S的對象,而不會破壞程序。
定義2:
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。
所有引用其父類對象方法的地方,都可以透明的替換為其子類對象
示例代碼:
下面是一個違反里氏替換原則的代碼示例。
違反里氏替換原則的代碼
假設我們有一個基類 Bird
和一個子類 Penguin
:
class Bird {public void fly() {System.out.println("I can fly");}
}class Penguin extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("Penguins can't fly");}
}
這個代碼示例中,Penguin
類重寫了 fly
方法并拋出異常,這違反了里氏替換原則,因為如果我們使用 Penguin
對象替換 Bird
對象,程序將會拋出異常,導致行為不一致。
遵循里氏替換原則的代碼
為了遵循里氏替換原則,我們可以引入一個接口 Flyable
,并讓 Bird
和其他可以飛的鳥類實現這個接口,而 Penguin
則不實現這個接口:
interface Flyable {void fly();
}class Bird {public void eat() {System.out.println("I can eat");}
}class Sparrow extends Bird implements Flyable {@Overridepublic void fly() {System.out.println("I can fly");}
}class Penguin extends Bird {// 企鵝沒有實現Flyable這個接口
}
在這個重構后的設計中,Penguin
類不再需要實現 fly
方法,從而避免了違反里氏替換原則。現在,如果我們有一個方法需要處理所有可以飛的鳥,我們可以使用 Flyable
接口:
public void letBirdFly(Flyable bird) {bird.fly();
}public static void main(String[] args) {Sparrow sparrow = new Sparrow();Penguin penguin = new Penguin();letBirdFly(sparrow); // This works// letBirdFly(penguin); // 這將導致編譯時錯誤
}
通過這種方式,我們確保了替換基類對象不會影響程序的行為,從而遵循了里氏替換原則。
里氏替換原則的優點
- 提高代碼的可維護性:遵循里氏替換原則,可以確保子類和基類的行為一致,減少代碼中的錯誤,提升代碼的可維護性。
- 增強代碼的可擴展性:通過接口和抽象類的使用,可以更容易地擴展系統,添加新的子類而不影響現有代碼。
- 增強代碼的可重用性:遵循里氏替換原則,可以提高代碼的重用性,使得基類和子類之間的關系更加明確和穩固。
依賴倒置原則
什么是依賴倒置原則
**依賴倒置原則(Dependency Inversion Principle,DIP)**是面向對象設計的五個SOLID原則之一。該原則強調:
- 高層模塊不應該依賴于低層模塊。二者都應該依賴于抽象。
- 抽象不應該依賴于具體實現。具體實現應該依賴于抽象。
簡單來說,依賴倒置原則提倡面向接口編程,而不是面向實現編程。這可以減少高層模塊與低層模塊之間的耦合,使系統更具靈活性和可擴展性。
依賴倒置原則的核心思想
- 依賴于抽象(接口或抽象類),而不是具體類:通過依賴于抽象,可以在不修改高層模塊的情況下更換低層模塊的實現。
- 通過依賴注入來實現依賴倒置:使用構造器注入、方法注入或屬性注入的方式,將具體實現傳遞給高層模塊。
依賴倒置原則的優點
- 降低耦合:高層模塊和低層模塊之間通過接口或抽象類解耦。
- 增強可維護性:修改低層模塊的實現不會影響高層模塊。
- 提高可擴展性:可以方便地替換或新增實現而不改變現有代碼。
示例代碼:
不遵循依賴倒置原則的設計
在這個例子中,Light
類和 Switch
類之間有直接的依賴關系:
// 燈類
class Light {public void turnOn() {System.out.println("Light is turned on.");}public void turnOff() {System.out.println("Light is turned off.");}
}// 開關類
class Switch {private Light light;public Switch() {this.light = new Light();}public void operate(String command) {if (command.equalsIgnoreCase("ON")) {light.turnOn();} else if (command.equalsIgnoreCase("OFF")) {light.turnOff();}}
}public class Main {public static void main(String[] args) {Switch lightSwitch = new Switch();lightSwitch.operate("ON");lightSwitch.operate("OFF");}
}
在這個設計中,Switch
類直接依賴于 Light
類,如果需要更換 Light
的實現,需要修改 Switch
類的代碼。
遵循依賴倒置原則的設計
在這個例子中,通過引入接口 Switchable
,實現依賴倒置原則:
// 開關接口
interface Switchable {void turnOn();void turnOff();
}// 燈類實現開關接口
class Light implements Switchable {public void turnOn() {System.out.println("Light is turned on.");}public void turnOff() {System.out.println("Light is turned off.");}
}// 開關類依賴于開關接口,而不是具體的實現
class Switch {private Switchable device;public Switch(Switchable device) {this.device = device;}public void operate(String command) {if (command.equalsIgnoreCase("ON")) {device.turnOn();} else if (command.equalsIgnoreCase("OFF")) {device.turnOff();}}
}public class Main {public static void main(String[] args) {Switchable light = new Light();Switch lightSwitch = new Switch(light);lightSwitch.operate("ON");lightSwitch.operate("OFF");}
}
在上面的設計中,Switch
類依賴于 Switchable
接口,而不是具體的 Light
類。如果將來需要更換實現,只需實現 Switchable
接口并傳遞新的實現給 Switch
類。
實際應用中的優點
- 增強代碼的可測試性:通過依賴注入,可以輕松地將實際實現替換為模擬對象,從而進行單元測試。
- 增加代碼的靈活性和可擴展性:通過依賴抽象,代碼可以適應不同的實現,而不需要修改高層模塊。
- 提高代碼的可維護性:代碼的變更只會影響具體實現,不會波及依賴于抽象的高層模塊。
接口隔離原則
什么是接口隔離原則
接口隔離原則(Interface Segregation Principle,ISP) 是面向對象設計的五個SOLID原則之一。該原則強調:
- 客戶不應該被迫依賴他們不使用的方法。
- 多個特定客戶端接口要好于一個寬泛用途的接口。
Clients should not be forced to depend upon interfaces that they don’t use. 客戶端只依賴于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。
The dependency of one class to another one should depend on the smallest possible interface. 類間的依賴關系應建立在最小的接口上。
換句話說,接口隔離原則提倡將大接口拆分為多個小接口,使得接口更具針對性和靈活性。這樣,客戶端只需依賴它們真正需要的接口,避免了冗余和不必要的依賴。
也就是說: 接口盡量細化,接口中的方法盡量少
示例代碼:
不遵循接口隔離原則的設計
在這個例子中,Worker
接口包含了所有工作者可能需要的方法,但具體的工作者類可能只需要其中的一部分:
// 工作者接口
public interface Worker {void work();void eat();
}// 開發者類
public class Developer implements Worker {@Overridepublic void work() {System.out.println("Developer is working.");}@Overridepublic void eat() {System.out.println("Developer is eating.");}
}// 機器人類
public class Robot implements Worker {@Overridepublic void work() {System.out.println("Robot is working.");}@Overridepublic void eat() {// 機器人不需要吃飯,但必須實現這個方法}
}
在這個設計中,Robot
類被迫實現了 eat
方法,這違反了接口隔離原則。
遵循接口隔離原則的設計
通過將 Worker
接口拆分為更細化的接口,可以避免上述問題:
// 工作接口
public interface Workable {void work();
}// 吃飯接口
public interface Eatable {void eat();
}// 開發者類實現了工作和吃飯接口
public class Developer implements Workable, Eatable {@Overridepublic void work() {System.out.println("Developer is working.");}@Overridepublic void eat() {System.out.println("Developer is eating.");}
}// 機器人類只實現了工作接口
public class Robot implements Workable {@Overridepublic void work() {System.out.println("Robot is working.");}
}
在這個設計中,Developer
類實現了 Workable
和 Eatable
接口,而 Robot
類只實現了 Workable
接口,遵循了接口隔離原則。
實際應用中的好處
- 提高靈活性:將大接口拆分為多個小接口,使得類可以選擇實現自己需要的接口,增加了系統的靈活性。
- 減少冗余:客戶端只依賴它們實際需要的接口,減少了不必要的方法實現。
- 增強可維護性:接口的細化使得系統更易于理解和維護,修改和擴展時影響范圍更小。
- 提高可測試性:小接口更容易進行單元測試,因為每個接口只包含了特定的功能方法。
示例代碼:
// 工具使用接口
public interface ToolUsable {void useTool();
}// 吃飯接口
public interface Eatable {void eat();
}// 工人類實現了工具使用和吃飯接口
public class Worker implements ToolUsable, Eatable {@Overridepublic void useTool() {System.out.println("Worker is using a tool.");}@Overridepublic void eat() {System.out.println("Worker is eating.");}
}// 機器人類只實現了工具使用接口
public class Robot implements ToolUsable {@Overridepublic void useTool() {System.out.println("Robot is using a tool.");}
}public class Main {public static void main(String[] args) {ToolUsable workerToolUser = new Worker();Eatable workerEater = new Worker();ToolUsable robotToolUser = new Robot();workerToolUser.useTool();workerEater.eat();robotToolUser.useTool();}
}
在這個示例中,我們將 Worker
類和 Robot
類的接口細化,使得它們只實現自己需要的接口,遵循了接口隔離原則。
迪米特原則
什么是迪米特法則
迪米特法則(Law of Demeter, LoD),又稱為最少知識原則(Principle of Least Knowledge),是一種軟件設計原則,其主要思想是:一個對象應該對其他對象有盡可能少的了解,即一個對象不應該知道太多不屬于它直接責任的對象細節。
如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用。如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。
朋友圈的確定“朋友”條件:
- 當前對象本身(this)
- 以參數形式傳入到當前對象方法中的對象. 方法入參是一個對象, 這是這個對象和當前類是朋友
- 當前對象的實例變量直接引用的對象 定一個一個類, 里面的屬性引用了其他對象, 那么這個對象的實例和當前實例是朋友
- 當前對象的實例變量如果是一個聚集,那么聚集中的元素也都是朋友 如果屬性是一個對象, 那么屬性和對象里的元素都是朋友
- 當前對象所創建的對象
任何一個對象,如果滿足上面的條件之一,就是當前對象的“朋友”;否則就是“陌生人”。
狹義的迪米特法則的缺點:
在系統里造出大量的小方法,這些方法僅僅是傳遞間接的調用,與系統的業務邏輯無關。 遵循類之間的迪米特法則會是一個系統的局部設計簡化,因為每一個局部都不會和遠距離的對象有直接的關聯。但是,這也會造成系統的不同模塊之間的通信效率降低,也會使系統的不同模塊之間不容易協調。
迪米特法則的規則
- 只調用自己的方法:對象只能調用自己方法,或者調用由自身創建的對象的方法。
- 只調用直接朋友的方法:對象只能調用作為參數傳遞給它的對象的方法,或是它的成員變量、全局變量的方法。
示例代碼:
違反迪米特法則的代碼
假設我們有一個 Car
類,它包含一個 Engine
對象,而 Engine
對象包含一個 Oil
對象:
class Oil {public void checkOilLevel() {System.out.println("Checking oil level");}
}class Engine {private Oil oil;public Engine() {this.oil = new Oil();}public Oil getOil() {return oil;}
}class Car {private Engine engine;public Car() {this.engine = new Engine();}public Engine getEngine() {return engine;}
}public class Main {public static void main(String[] args) {Car car = new Car();// 違反迪米特法則的代碼:直接訪問內部對象的內部對象的方法car.getEngine().getOil().checkOilLevel();}
}
在上面的例子中,Main
類通過 Car
對象訪問 Engine
對象,再通過 Engine
對象訪問 Oil
對象,最終調用 checkOilLevel
方法。這違反了迪米特法則,因為 Main
類知道了太多關于 Engine
和 Oil
的細節。
遵循迪米特法則的代碼
我們可以通過在 Car
類中添加一個方法,來避免直接訪問內部對象的內部對象:
class Oil {public void checkOilLevel() {System.out.println("Checking oil level");}
}class Engine {private Oil oil;public Engine() {this.oil = new Oil();}public void checkOilLevel() {oil.checkOilLevel();}
}class Car {private Engine engine;public Car() {this.engine = new Engine();}public void checkOilLevel() {engine.checkOilLevel();}
}public class Main {public static void main(String[] args) {Car car = new Car();// 遵循迪米特法則的代碼:只調用直接對象的方法car.checkOilLevel();}
}
在這個重構后的例子中,Main
類只調用了 Car
對象的方法 checkOilLevel
,Car
對象內部處理了所有與 Engine
和 Oil
對象的交互。這遵循了迪米特法則,降低了對象之間的耦合度。
迪米特法則的優點
- 降低耦合度:減少對象之間的依賴關系,使得系統更容易維護和擴展。
- 提高內聚性:每個對象只關注自身的職責,增強了代碼的內聚性。
- 增強可讀性:減少了代碼的復雜性,使代碼更容易理解和閱讀。
開閉原則
什么是開閉原則
開閉原則(Open-Closed Principle, OCP)是面向對象設計中的重要原則之一,由 Bertrand Meyer 于 1988 年提出。它是 SOLID 原則中的第二個,指的是軟件實體(類、模塊、函數等)應該對擴展開放,對修改關閉。這意味著一個系統在需要改變的時候,應該通過擴展已有代碼的行為來實現,而不是修改已有的代碼。
如何實現開閉原則
“需求總是變化”、“世界上沒有一個軟件是不變的”。這里投射出的意思是:需求總是變化的, 可是對于軟件設計者來說, 如何才能做到不對原有系統修改的前提下, 實現靈活的擴展. 這就是開閉原則要實現的。
我們在設計系統的時候, 不可能設想一次性把需求確定后, 后面就不改變了.這不科學也不現實的. 既然需求是一定會變化的, 那么我們要如何優雅的面對這種變化呢? 如何設計可以使軟件相對容易修改, 不至于需求一變, 就要把整個程序推到重來?
開封-封閉原則. 設計軟件要容易維護且不容易出問題的最好辦法, 就是多擴展, 少修改。
開閉原則通常通過使用抽象和多態性來實現。具體來說,可以通過以下幾種方法:
- 使用接口或抽象類:定義一個接口或抽象類,并通過不同的實現類來擴展功能。
- 使用設計模式:策略模式、裝飾器模式、工廠模式等設計模式都可以幫助實現開閉原則。
示例代碼:
場景描述
假設我們要開發一個簡單的繪圖應用程序,該程序可以繪制不同的形狀(如圓形、矩形)。我們希望能夠在不修改現有代碼的情況下,輕松添加新的形狀。
違反開閉原則的設計
以下代碼在添加新形狀時需要修改 ShapeDrawer
類,違反了開閉原則:
class ShapeDrawer {public void draw(String shapeType) {if (shapeType.equals("circle")) {System.out.println("Drawing a circle");} else if (shapeType.equals("rectangle")) {System.out.println("Drawing a rectangle");}// 添加新形狀時需要修改此處代碼}
}public class Main {public static void main(String[] args) {ShapeDrawer drawer = new ShapeDrawer();drawer.draw("circle");drawer.draw("rectangle");}
}
遵循開閉原則的設計
我們可以使用策略模式,通過抽象類或接口來擴展新形狀,而不需要修改現有的代碼:
// 定義抽象類 Shape
abstract class Shape {public abstract void draw();
}// 圓形類
class Circle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a circle");}
}// 矩形類
class Rectangle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a rectangle");}
}// 使用策略模式的繪圖類
class ShapeDrawer {private Shape shape;public void setShape(Shape shape) {this.shape = shape;}public void drawShape() {shape.draw();}
}public class Main {public static void main(String[] args) {ShapeDrawer drawer = new ShapeDrawer();drawer.setShape(new Circle());drawer.drawShape();drawer.setShape(new Rectangle());drawer.drawShape();// 可以通過添加新類來擴展新形狀,而無需修改 ShapeDrawer 類class Triangle extends Shape {@Overridepublic void draw() {System.out.println("Drawing a triangle");}}drawer.setShape(new Triangle());drawer.drawShape();}
}
在這個示例代碼中,我們定義了一個抽象類 Shape
,并為每種形狀創建一個具體實現類。ShapeDrawer
類通過組合一個 Shape
對象來繪制形狀。在需要添加新形狀時,只需創建一個新的形狀類并實現 Shape
抽象類,而無需修改 ShapeDrawer
類的代碼。這就實現了開閉原則。
開閉原則的優點
- 提高可維護性:減少了對已有代碼的修改,降低了引入新錯誤的風險。
- 提高可擴展性:通過擴展現有代碼來實現新功能,而不是修改現有代碼,增加了系統的靈活性。
- 提高復用性:抽象和實現分離,使得代碼更易于復用。
總結🍭
在軟件設計中,遵循設計原則有助于提高代碼的可維護性、可擴展性和復用性。
- 單一職責原則(SRP):
- 核心思想:每個類應當只有一個引起其變化的原因,即一個類只負責一項職責。
- 優勢:提高代碼的可讀性和可維護性,降低類之間的耦合度,增強系統的靈活性。
- 示例:將生成報告和打印報告的功能分離到不同的類中。
- 里氏替換原則(LSP):
- 核心思想:子類必須能夠替換基類而不影響程序的正確性。
- 優勢:確保子類能夠正確擴展基類功能,提高代碼的穩定性和可擴展性。
- 示例:通過接口和抽象類實現多態性,避免子類破壞基類的行為。
- 迪米特法則(LoD):
- 核心思想:一個對象應當盡可能少地了解其他對象,即只與直接的朋友通信。
- 優勢:降低對象之間的耦合度,提高系統的內聚性和可維護性。
- 示例:通過在
Car
類中添加方法來避免直接訪問內部對象的內部對象。
- 開閉原則(OCP):
- 核心思想:軟件實體應當對擴展開放,對修改關閉。
- 優勢:通過擴展現有代碼來實現新功能,而不是修改已有代碼,減少引入新錯誤的風險,提高系統的靈活性和可擴展性。
- 示例:使用策略模式,通過抽象類或接口來擴展新形狀,而不修改現有代碼。
在實際開發中,這些設計原則通常是相輔相成的。 例如,通過遵循單一職責原則,可以提高類的內聚性和可維護性,而結合里氏替換原則和開閉原則,可以設計出更靈活和可擴展的系統。此外,迪米特法則可以進一步降低類之間的耦合度,提高系統的健壯性。
通過理解和應用這些設計原則,可以構建出更高質量的軟件系統,減少后期維護的復雜性,并提高開發效率和代碼復用性。這些原則不僅適用于面向對象編程,也同樣適用于其他編程范式,是軟件開發過程中不可或缺的指導思想。