一、設計模式的目的
設計模式主要是為了解決在編寫程式碼過程中,面臨的耦合性、內聚性、可維護性、可擴充套件行、重用性、靈活性等多方面的挑戰。
- 程式碼重用性:相同功能的程式碼不用多次編寫
- 可讀性:程式設計規範,便於他人的閱讀和理解
- 可擴充套件性:當需要新增新功能時,非常的方便
- 可靠性:當增加新的功能後,對原來的功能沒有影響
- 最終達到高內聚低耦合的特性
二、設計模式七大原則
設計模式原則,其實就是程式設計師在程式設計時,應當遵守的原則,也是各種設計模式的基礎。
- 單一職責原則
- 介面隔離原則
- 依賴倒轉(倒置)原則
- 里氏替換原則
- 開閉原則
- 迪米特法則
- 合成複用原則
三、單一職責原則
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"); } }
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"); } }
五、依賴倒轉原則
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(); }
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(); } }
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(); } }
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)為了互動物件之間的鬆耦合設計而努力