設計模式的七大原則
一、設計模式的目的
設計模式主要是為了解決在編寫代碼過程中,面臨的耦合性、內聚性、可維護性、可擴展行、重用性、靈活性等多方面的挑戰。
- 代碼重用性:相同功能的代碼不用多次編寫
- 可讀性:編程規範,便於他人的閱讀和理解
- 可擴展性:當需要添加新功能時,非常的方便
- 可靠性:當增加新的功能後,對原來的功能沒有影響
- 最終達到高內聚低耦合的特性
二、設計模式七大原則
設計模式原則,其實就是程序員在編程時,應當遵守的原則,也是各種設計模式的基礎。
- 單一職責原則
- 接口隔離原則
- 依賴倒轉(倒置)原則
- 里氏替換原則
- 開閉原則
- 迪米特法則
- 合成復用原則
三、單一職責原則
1,基本介紹
對類來說的,即一個類應該只負責一項職責。如類A負責兩個不同職責:職責1,職責2。當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2
2,應用實例
a)方案1
/** * 方案1:違反單一職責,所有的交通工具採用同一種方式運行 */ public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托車"); vehicle.run("汽車"); vehicle.run("飛機"); } } class Vehicle{ public void run(String vehicle) { System.out.println(vehicle + " 在公路上運行...."); } }
b)方案2
/** * 方案2的分析 * 1. 遵守單一職責原則 * 2. 但是這樣做的改動很大,即將類分解,同時修改客戶端 * 3. 改進:直接修改Vehicle 類,改動的代碼會比較少=>方案3 */ public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("摩托車"); roadVehicle.run("汽車"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飛機"); } } class RoadVehicle { public void run(String vehicle) { System.out.println(vehicle + " 公路運行"); } } class AirVehicle { public void run(String vehicle) { System.out.println(vehicle + " 天空運行"); } }
c)方案3
/** * 方式3的分析 * 1. 這種修改方法沒有對原來的類做大的修改,只是增加方法 * 2. 這裡雖然沒有在類這個級別上遵守單一職責原則,但是在方法級別上,仍然是遵守單一職責 */ public class SingleResponsibility3 { public static void main(String[] args) { Vehicle2 vehicle2 = new Vehicle2(); vehicle2.run("汽車"); vehicle2.runWater("輪船"); vehicle2.runAir("飛機"); } } class Vehicle2 { public void run(String vehicle) { System.out.println(vehicle + " 在公路上運行...."); } public void runAir(String vehicle) { System.out.println(vehicle + " 在天空上運行...."); } public void runWater(String vehicle) { System.out.println(vehicle + " 在水中行...."); } }
3,注意事項
- 降低類的複雜度,一個類只負責一項職責
- 提交類的可讀性,可維護性
- 降低變更引起的風險
- 通常情況下,我們應當遵守單一職責原則。只有邏輯足夠簡單,才可以在代碼級違反單一職責原則;只有類中方法數量足夠少,才可以在方法級保持單一職責原則。
四、接口隔離原則
1,基本介紹
客戶端不應該依賴它不需要的接口,即一個類對另一個類的依賴應該建立在最小的接口上。
2,應用案例
類A通過接口Interface1依賴類B,類C通過接口Interface1依賴類D。但是類A只需要操作類B中的operation1、operation2和operation3,同時類C也只需要操作類D中的operation1、operation4和operation5,但是由於類B和類D都是接口Interface1的實現類,故而都重寫了不需要的方法。
源碼:基本案例


public interface Interface1 { void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } public abstract class A { public void dependecy1(Interface1 interface1) { interface1.operation1(); } public void dependecy2(Interface1 interface1) { interface1.operation2(); } public void dependecy3(Interface1 interface1) { interface1.operation3(); } } public class B implements Interface1{ @Override public void operation1() { System.out.println("B實現了operation1"); } @Override public void operation2() { System.out.println("B實現了operation2"); } @Override public void operation3() { System.out.println("B實現了operation3"); } @Override public void operation4() { System.out.println("B實現了operation4"); } @Override public void operation5() { System.out.println("B實現了operation5"); } } public class C { public void dependecy1(Interface1 interface1) { interface1.operation1(); } public void dependecy4(Interface1 interface1) { interface1.operation4(); } public void dependecy5(Interface1 interface1) { interface1.operation5(); } } public class D implements Interface1{ @Override public void operation1() { System.out.println("D實現了operation1"); } @Override public void operation2() { System.out.println("D實現了operation2"); } @Override public void operation3() { System.out.println("D實現了operation3"); } @Override public void operation4() { System.out.println("D實現了operation4"); } @Override public void operation5() { System.out.println("D實現了operation5"); } }
View Code
3,基於接口隔離原則的改進
將接口Interface1拆分為獨立的幾個接口,類A和類C分別與他們需要的接口建立依賴關係,即採用接口隔離原則。
源碼:接口隔離實現


public interface Interface1 { void operation1(); } public interface Interface2 { void operation2(); void operation3(); } public interface Interface3 { void operation4(); void operation5(); } public class A { public void dependecy1(Interface1 interface1) { interface1.operation1(); } public void dependecy2(Interface2 interface2) { interface2.operation2(); } public void dependecy3(Interface2 interface2) { interface2.operation3(); } } public class B implements Interface1, Interface2 { @Override public void operation1() { System.out.println("B實現了operation1"); } @Override public void operation2() { System.out.println("B實現了operation2"); } @Override public void operation3() { System.out.println("B實現了operation3"); } } public class C { public void dependecy1(Interface1 interface1) { interface1.operation1(); } public void dependecy4(Interface3 interface3) { interface3.operation4(); } public void dependecy5(Interface3 interface3) { interface3.operation5(); } } public class D implements Interface1, Interface3 { @Override public void operation1() { System.out.println("D實現了operation1"); } @Override public void operation4() { System.out.println("D實現了operation4"); } @Override public void operation5() { System.out.println("D實現了operation5"); } }
View Code
五、依賴倒轉原則
1,基本介紹
- 高層模塊不應該依賴底層模塊,二者都應該依賴其抽象
- 抽象不應該依賴細節,細節應該依賴抽象
- 依賴倒轉(倒置)的中心思想是面向接口編程
- 依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多。以抽象為基礎搭建的架構比以細節為基礎的架構要穩定的多。在java中,抽象指的是接口或抽象類,細節就是具體的實現類
- 使用接口或抽象類的目的是制定好規範,而不涉及任何具體的操作,把展示細節的任務交給他們的實現類去完成
2,應用案例
a)方案1
/** * 方案1:完成Person接受消息的功能 * 簡單易懂 * 如果我們獲取的對象是 微信、短訊等,則新增類,同時Person也要增加對應的接收方法 */ public class Person { public void receive(Email email){ System.out.println(email.getInfo()); } } class Email{ public String getInfo() { return "電子郵件信息:hello word"; } }
b)方案2改進
/** * 方案2:依賴倒置原則 * 引入一個抽象的接口IReceiver接口,讓不同的接收消息方法都實現這個接口重寫getInfo方法 * 由調用方決定傳入的具體對象 */ public class Person { public void receive(IReceiver receiver) { System.out.println(receiver.getInfo()); } } interface IReceiver{ public String getInfo(); } class Email implements IReceiver{ @Override public String getInfo() { return "電子郵件信息:hello word"; } } class WeiXin implements IReceiver { @Override public String getInfo() { return "微信信息:hello wechat"; } }
3,依賴關係傳遞的三種方式(同Spring DI)
a)接口傳遞


/** * 接口傳遞的方式,將接口ITV傳遞open方法 */ class OPenAndClose implements IOPenAndClose { @Override public void open(ITV itv) { itv.play(); } } interface IOPenAndClose { public void open(ITV itv); } interface ITV { public void play(); }
View Code
b)構造方法傳遞


//方式二:構造方法傳遞 interface IOpenAndClose { public void open();//抽象方法 } interface ITV {//ITV接口 public void play(); } class OpenAndClose implements IOpenAndClose { public ITV tv;//成員 public OpenAndClose(ITV tv) {//構造方法 this.tv = tv; } @Override public void open() { this.tv.play(); } }
View Code
c)setter傳遞


//方式三:setter方法傳遞 interface IOpenAndClose { public void open();//抽象方法 } interface ITV {//ITV接口 public void play(); } class OpenAndClose implements IOpenAndClose { private ITV tv; public void setTv(ITV tv) { this.tv = tv; } @Override public void open() { this.tv.play(); } }
View Code
4,注意事項和細節
- 低層模塊盡量都要有抽象類和接口,或者兩者都有,提高程序的穩定性
- 變量的聲明類型盡量時抽象類或接口,這樣我們的變量引用和實際對象間就存在一個緩衝層,利於程序擴展和優化
- 繼承時遵循里氏替換原則
六、里氏替換原則
1,OO中繼承性的思考和說明
- 繼承:父類中凡是已經實現好的方法,實際上是在設定規範和契約,雖然它不強制要求所有的子類必須遵循這些契約,但是如果子類對這些已經實現的方法任意修改,就會對整個繼承體系造成破壞。
- 繼承在程序設計上帶來了便利的同時,也帶來了弊端。比如使用繼承會給程序帶來侵入性,程序的可移植性降低,增加對象間的耦合性。如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮所有的子類,並且父類修改後,所有涉及到子類的功能都有可能產生故障。
2,基本介紹
- 如果對每一個類型為T1的對象o1,都有類型為T2的對象o2,使得以T1定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有發生變化,那麼類型T2是類型T1的子類型。
- 所有引用基類的地方必須能透明地使用其子類的對象。
- 在使用繼承時,遵循里氏替換原則,在子類中盡量不要重寫父類的方法
- 繼承實際上讓兩個類的耦合性增強了,在適當的情況下,可以通過聚合、組合、依賴來處理
3,應用案例
a)方案1
public class A { public int fuc1(int a, int b) { return a - b; } } /** * 類B繼承A * 由於B無意識重寫了A的fuc1方法,導致最終調用時發現預期類A的fuc1不生效 */ class B extends A { public int fuc1(int a, int b) { return a + b; } public int fuc2(int a, int b) { return a * b; } }
b)方案2改進
//定義基類 public class Base { } class A extends Base { public int fuc1(int a, int b) { return a - b; } } /** * 類A和類B繼承Base類 * B使用組合的方式使用A,這樣fuc3仍然是類A的方法 */ class B extends Base { A a = new A(); //這裡,重寫了 A 類的方法, 可能是無意識 public int fuc1(int a, int b) { return a + b; } public int fuc2(int a, int b) { return fuc1(a, b) + 9; } //我們仍然想使用 A 的方法 public int fuc3(int a, int b) { return this.a.fuc1(a, b); } }
七、開閉原則
1,基本介紹
- 開閉原則是編程中最基礎、最重要的設計原則
- 一個軟件實體如類、模塊和函數應該對擴展開發(指對提供方開放),對修改關閉(指對使用方關閉)。用抽象構建框架,用實現擴展細節
- 當軟件需要變化時,盡量通過擴展軟件實體的行為來實現變化,而不是通過修改已有的代碼來實現變化。(盡量增加一種功能/擴展,而不是修改這部分可能正在使用的功能)
- 編程中遵循其他原則,以及使用設計模式的目的就是遵循開閉原則
2,應用案例
a)方案1
public class OCP { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.drawShape(new Rectangle()); graphicEditor.drawShape(new Circle()); } } //繪圖的類,使用方 class GraphicEditor{ //根據不同的type繪製不同的圖 public void drawShape(Shape s) { if (s.type == 1) { drawRectangle(s); } else if (s.type == 2) { drawRectangle(s); } } public void drawRectangle(Shape r) { System.out.println("繪製矩形"); } public void drawCircle(Shape r) { System.out.println("繪製圓形"); } } //基類 abstract class Shape{ int type; } class Rectangle extends Shape { public Rectangle() { super.type = 1; } } class Circle extends Shape { public Circle() { super.type = 2; } }
問題:該方案違反了設計模式的開閉原則,當需要增加一個圖形種類(例:三角形),不僅需要新建類,而且使用方代碼也需要添加適配
b)方案2改進
public class OCP { public static void main(String[] args) { GraphicEditor graphicEditor = new GraphicEditor(); graphicEditor.draw(new Rectangle()); graphicEditor.draw(new Circle()); } } class GraphicEditor{ public void draw(Shape s) { s.draw(); } } abstract class Shape{ public abstract void draw(); } class Rectangle extends Shape { @Override public void draw() { System.out.println("繪製矩形"); } } class Circle extends Shape { @Override public void draw() { System.out.println("繪製圓形"); } }
八、迪米特法則
1,基本介紹
- 一個對象應該對其他對象保持最少的了解
- 類與類關係越密切,耦合度越大
- 迪米特法則又叫最少知道原則,即一個類對自己依賴的類知道的越少越好。也就是說,對於被依賴的類不管多麼複雜,都盡量將邏輯封裝在類的內部。對外除了提供public方法,不對外泄露任何信息
- 迪米特法則還有個更簡單的定義:只與直接的朋友通信
- 直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這兩個對象之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們成出現成員變量、方法參數、方法返回值中的類為直接朋友,而出現在局部變量中的類不是直接的朋友。也就是說,陌生的類最好不要以局部變量的形式出現在類的內部。
2,應用實例
有一個學校,下屬有各個學院和總部,現要求打印出學校總部員工 ID 和學院員工的 id
a)方案1
public class Demeter { public static void main(String[] args) { SchoolManage manage = new SchoolManage(); manage.printAllEmp(new CollegeManage()); } } //學校總部員工 class Employee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //學院員工 class CollegeEmployee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //學院管理 class CollegeManage { public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { CollegeEmployee emp = new CollegeEmployee(); emp.setId("學院員工 id= " + i); list.add(emp); } return list; } } //學校總部管理 class SchoolManage { public List<Employee> getAllEmployee() { List<Employee> list = new ArrayList<Employee>(); for (int i = 0; i < 10; i++) { Employee emp = new Employee(); emp.setId("學校總部員工 id= " + i); list.add(emp); } return list; } //這裡的 CollegeEmployee 不是 SchoolManager 的直接朋友 //違反了迪米特法則 public void printAllEmp(CollegeManage collegeManage) { List<CollegeEmployee> allEmployee = collegeManage.getAllEmployee(); System.out.println("------------學院員工------------"); for (CollegeEmployee e : allEmployee) { System.out.println(e.getId()); } //獲取到學校總部員工 List<Employee> list2 = this.getAllEmployee(); System.out.println("------------學校總部員工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }
問題:由於在SchoolManage中,CollegeEmployee類並不是SchoolManage類的直接朋友。按照迪米特法則,應該避免類中出現這樣非直接朋友關係的耦合
b)方案2
優化:完整代碼
//學院管理 class CollegeManage { public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<CollegeEmployee>(); for (int i = 0; i < 10; i++) { CollegeEmployee emp = new CollegeEmployee(); emp.setId("學院員工 id= " + i); list.add(emp); } return list; } //在學院管理中增加遍歷類,方便直接被調用 public void printCollegeEmp() { List<CollegeEmployee> allEmployee = this.getAllEmployee(); System.out.println("------------學院員工------------"); for (CollegeEmployee e : allEmployee) { System.out.println(e.getId()); } } } //學校總部管理 class SchoolManage { public List<Employee> getAllEmployee() { List<Employee> list = new ArrayList<Employee>(); for (int i = 0; i < 10; i++) { Employee emp = new Employee(); emp.setId("學校總部員工 id= " + i); list.add(emp); } return list; } public void printAllEmp(CollegeManage collegeManage) { collegeManage.printCollegeEmp(); //獲取到學校總部員工 List<Employee> list2 = this.getAllEmployee(); System.out.println("------------學校總部員工------------"); for (Employee e : list2) { System.out.println(e.getId()); } } }
3,注意事項
- 迪米特法則的核心是降低類之間的耦合
- 由於每個類都減少了不必要的依賴,因此迪米特法則只是要求降低類間(對象間)耦合關係,並不是要求完全沒有依賴關係
九、合成復用原則
1,定義
- 合成復用原則是指盡量使用對象組合/聚合,而不是繼承關係達到軟件復用的目的。可以說使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其他類造成的影響相對較少。
- 繼承我們叫做白箱復用,相當於把所有的實現細節暴露給子類。組合/聚合也稱之為黑箱復用,對類以外的對象是無法獲取到實現細節的。要根據具體的業務場景來做代碼設計,其實也都要遵循OOP模型。
十、 設計原則核心思想
- 1)找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
- 2)針對接口編程,而不是針對實現編程。
- 3)為了交互對象之間的松耦合設計而努力