設計模式--7大原則
問題:編寫程式碼過程中,程式設計師面臨著來自耦合性,內聚性以及可維護性,可擴充套件性,重用性,靈活性等挑戰.
設計模式的好處
1、提到程式碼衝用心(即,相同程式碼,不用多次編寫)
2、可讀性(即,程式設計規範性,便於其他程式設計師的閱讀和理解)
3、可擴充套件性(即,當需要增加新的功能時,非常的方便,成為可維護性)
4、可靠性(即,當我們增加新的功能後,對原來的功能沒有影響)
5、使程式高內聚、低耦合
設計模式原則,其實就是程式設計師在程式設計時,應當遵守的原則,也是各種設計模式的基礎(即,設計模式為什麼這樣設計的依據)
一、單一職責原則
基本介紹
對類來說,即一個類應該只負責一項職責(不是一個方法
當職責1需求變更而改變A時,可能造成職責2執行錯誤,所以需要將類A的粒度分解為A1,A2.
應用例項
做專案時的單一職責,如userDao,只做user的資料庫相關功能.
程式碼:
錯誤示範
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托車"); vehicle.run("汽車"); vehicle.run("飛機"); } } /** * 交通工具類 * 1、在方式1的run方法中,違反了單一職責原則 * 2、解決的方案非常簡單,根據交通工具執行方法不同,分解為不同的類即可 */ class Vehicle { public void run(String vehicle) { System.out.println(vehicle + "running on the load..."); } }
改進
public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle= new RoadVehicle(); roadVehicle.run("摩托車"); roadVehicle.run("汽車"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飛機"); } } /** * 方案2分析 * 1、遵守單一職責原則 * 2、但是這樣做改動很大,即將類分解,同時修改客戶端 * 3、改進:直接修改Vehicle 類,改動的程式碼會比較少=》方案3 */ class RoadVehicle { public void run(String vehicle) { System.out.println(vehicle + " running on the load..."); } } class AirVehicle { public void run(String vehicle) { System.out.println(vehicle + " flying in the sky..."); } } class WaterVehicle { public void run(String vehicle) { System.out.println(vehicle + " running in the water..."); } }
改進
public class SingleResponsibility3 { public static void main(String[] args) { Vehicle2 vehicle2 = new Vehicle2(); vehicle2.run("汽車"); vehicle2.runWater("輪船"); vehicle2.runAir("飛機"); } } /** * 方式3分析 * 1、這種修改方法沒有對原來的類做大的修改,只是增加方法 * 2、這裡雖然沒有在類這個級別上遵守單一職責原則,但是在方法級別上,仍然是遵守單一職責原則 */ class Vehicle2 { public void run(String vehicle) { System.out.println(vehicle + " running on the load..."); } public void runAir(String vehicle) { System.out.println(vehicle + " flying in the sky..."); } public void runWater(String vehicle) { System.out.println(vehicle + " running in the water..."); } }
小結
單一職責原則注意事項和細節
1、降低類的複雜度,一個類只負責一項職責
2、提高類的可讀性,可維護性
3、降低變更引起的風險
4、通常情況下,我們應當遵守單一職責原則,只有邏輯足夠簡單,才可以在程式碼級違反單一職責原則;只有類中方法數量足夠多,可以在方法級別保持單一職責原則
a)如:車輛的出行方式,如果只有很少的出行方式,那麼只需要幾個方法就夠了(例如:電動車、自動車、小汽車),但是如果有很多出行方式,那麼最好拆分為多個類(例如:路上跑的、水上跑的,天上飛的),每個類負責不同型別車輛的出行方式
單一職責原則的一點思考,什麼時候拆分為多個類?
當只需要幾個方法就需要完成,類的方法邏輯簡單時,可以只用一個類(多個類開銷大),但是如果有很多方法,儘量拆分為多個類,每個類負責一批方法.
二、介面隔離原則(interface Segregation Principle)
基本介紹
1、客戶端不應該依賴它不需要的介面,即一個類的依賴應該建立在最小的介面上
依賴介面時,希望我們所依賴的介面是最小的,用不到的方法應該進行隔離,隔離所用的方法就是介面隔離原則把介面進行拆分
2、如圖所示
3、類A通過介面Interface依賴類B,類C通過介面Interface1依賴類D,如果介面Interface1對於Interface1對於類A和類C來說不是最小介面,那麼類B和類D必須去實現他們不需要的方法
4、介面隔離原則應該這樣處理
a)將介面Interface1拆分為獨立的幾個介面(這裡我們拆分為3個介面),類A和類C分別與他們需要的介面建立依賴關係.也就是介面隔離原則.
應用例項
1、類A通過介面Interface1依賴類B,類C通過介面Interface1依賴類D,請編寫程式碼完成此應用例項
錯誤示範:
interface Interface1 { void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } 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"); } } 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"); } } /** * C類通過介面Interface1依賴(使用)D類,但是隻會用到1,4,5方法 */ class C { public void depend1(Interface1 i) { i.operation1(); } public void depend4(Interface1 i) { i.operation4(); } public void depend5(Interface1 i) { i.operation5(); } }
應傳統方法的問題和使用介面隔離原則進行改進
1、類A通過介面Interface依賴類B,類C通過Interface1依賴類D,如果介面Interface1對於類A和類C來說不是最小介面,那麼類B和類D必須去實現他們不需要的方法
2、將介面Interface1拆分為獨立的幾個介面,類A和類C分別與他們需要的介面建立依賴關係.也就是採用介面隔離原則
3、介面Interface1出現的方法,根據實際情況拆分為3個介面
程式碼改進
public class Segregation1 { public static void main(String[] args) { } } /** * 介面1 */ interface Interface1 { void operation1(); } /** * 介面2 */ interface Interface2 { void operation2(); void operation3(); } /** * 介面3 */ interface Interface3 { void operation4(); void operation5(); } 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"); } } 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"); } } /** * A類通過介面Interface1,INterface2依賴(使用)B類,但是隻會用到1,2,3方法 */ class A { public void depend1(Interface1 i) { i.operation1(); } public void depend2(Interface2 i) { i.operation2(); } public void depend3(Interface2 i) { i.operation3(); } } /** * C類通過介面Interface1,Interface3依賴(使用)D類,但是隻會用到1,4,5方法 */ class C{ public void depend1(Interface1 i) { i.operation1(); } public void depend2(Interface3 i) { i.operation4(); } public void depend3(Interface3 i) { i.operation5(); } }
三、依賴倒轉原則
基本介紹
依賴倒轉原則是指
1、高層模組不應該依賴底層模組,二者都應該依賴其抽象
2、抽象不應該依賴細節,細節應該依賴抽象
3、依賴倒轉(倒置)的中心思想是面向介面程式設計
4、依賴倒轉原則是基於這樣的設計理念:相對於細節的多變性,抽象的東西要穩定的多.以抽象為基礎搭建的架構比細節為基礎的架構要穩定的多.在java中,抽象指的是介面或抽象類,細節就是具體的實現類
5、使用介面或抽象類的目的是制訂好規範,而不涉及任何具體的操作,把展現細節的任務交給他們的實現類去完成
應用例項
請完成Person接收訊息的功能
1、實現方案1
public class DependencyInversion { public static void main(String[] args) { Person person = new Person(); person.receive(new Email()); } } class Email { public String getInfo() { return "電子郵件資訊:helloword"; } } /** * 完成Person接收訊息的功能 * 方式1分析 * 1、簡單,比較容易想到 * 2、如果我們獲取的物件是微信,簡訊等,則新增類,同時Persons也要增加相應接受方法 * 3、解決思路:引入一個抽象的介面,IReceiver,表示接受者,這樣Person與介面IReceiver發生依賴 * 因為Email,WeChat等等屬於接收的範圍,他們各自實現IReceiver介面就ok,這樣我們就符合依賴倒轉原則 */ class Person { public void receive(Email email) { System.out.println(email.getInfo()); } }
改進
public class DependencyInversion { public static void main(String[] args) { //客戶端無需改變 //細節依賴於抽象 Person person = new Person(); person.receive(new Email()); person.receive(new WeChat()); } } interface IReceiver { public String getInfo(); } class Email implements IReceiver { @Override public String getInfo() { return "電子郵件資訊:hello world"; } } class WeChat implements IReceiver { @Override public String getInfo() { return "微信資訊:hello weChat"; } } class Person { public void receive(IReceiver receiver) { //抽象不依賴於細節 System.out.println(receiver.getInfo()); } }
依賴關係傳遞的三種方式和應用案例
1、介面傳遞
2、構造方法傳遞
3、setter方法傳遞
應用案例程式碼
介面傳遞
/** * 方式1:通過介面傳遞實現依賴 * 開關介面 */ interface IOpenAndClose{ /** * 抽象方法,接收介面 * @param itv */ public void open(ITV itv); } /** * ITV介面 */ interface ITV { public void ploy(); } class ChangHong implements ITV{ @Override public void ploy() { System.out.println("開啟長虹電視。。。"); } } /** * 實現介面:通過介面傳遞 */ class OpenAndClose implements IOpenAndClose{ @Override public void open(ITV tv) { tv.ploy(); } }
構造器傳遞
/** * 方式2 * 構造器傳遞 */ interface IOpenAndClose { /** * 抽象方法,接收介面 */ public void open(); } interface ITV { public void ploy(); } class OpenAndClose implements IOpenAndClose { ITV tv; OpenAndClose(ITV tv){ this.tv = tv; } @Override public void open() { tv.ploy(); } }
setter方法傳遞
/** * 放鬆3:setter方法注入 */ interface IOpenAndClose{ public void open(); public void setTv(ITV itv); } interface ITV{ public void ploy(); } class OpenAndClose implements IOpenAndClose{ ITV tv; @Override public void open() { tv.ploy(); } @Override public void setTv(ITV itv) { this.tv = itv; } }
其實這三種傳遞方式有一個共性,就是ITV、IOpenAndClose、OpenAndClose三者承擔起了整個架構,基本不變,其他具體tv只要實現ITV介面,就可以保持依賴倒置原則.
依賴倒轉原則的注意事項和細節
1、底層模組儘量都要有抽象類或介面,或者兩者都有,程式穩定性更好
2、變數的生命型別儘量是介面或抽象型別,這樣我們的變數引用和實際物件間,就存在一個緩衝層,利於程式擴充套件和優化
3、繼承時遵循里氏替換原則