7种 Java 设计模式,你会几种?
為什么要學習設計模式
設計模式并不是什么新的知識,它只是一種經驗的總結,所以必然是先有人這么去做了,然后才有人去總結提煉,從而變成了設計模式。
那么既然設計模式是前人總結的經驗,我們何不站在巨人的肩膀上,去體會經驗帶來的好處呢?
所以我們在學習設計模式的過程中,最重要的是掌握其中的設計思想,而設計模式最重要的思想就是解耦。我們需要將其解耦思想為自己所用,從而提升自己編碼能力,使自己的代碼更加容易維護、擴展。
軟件設計七大原則
在軟件開發過程中,為了提高系統的可維護性、可復用性、可擴展性以及靈活性,產生了七大設計原則,這些原則也會貫穿體現在設計模式中。設計模式會盡量遵循這些原則,但是也可能為了某一個側重點從而犧牲某些原則,在我們日常開發中也只能說盡量遵守,但是并不必為了遵守而遵守。
開閉原則
開閉原則:Open-Closed Principle,簡稱為?OCP。其核心是指在一個軟件實體中(如類,函數等),我們應該對擴展開放、對修改關閉,這樣就可以提高軟件系統的可復用性和可維護性。
開閉原則是面向對象設計的最基本原則,而遵守開閉原則的核心思想就是面向抽象編程。
下面我們以超市中的商品為例進行說明,請大家跟著我一起完成這個實驗。
因為我們有七大原則需要講解,有些原則之間類會同名,為了方便區分,我們以每一個原則的簡稱來新建一個目錄,比如開閉原則新建的目錄名為?ocp,然后相關的類就創建在?ocp?目錄下。
- 新建一個商品接口?IGoods.java,接口中定義了兩個方法:一個獲取商品名,一個獲取商品出售價。
- 新建一個具體商品蔬菜類?Cabbage.java?來實現商品接口。
上面我們看到,蔬菜售價是 3.98/kg,那么這時候到了晚上,需要打折,售價要改為 1.98/kg,這時候普通的做法有三種選擇:
- 直接修改?Cabbage?類的?getSalePrice。
- 接口中再新增一個打折價方法。
- 直接在?Cabbage?方法中新增一個獲取打折后價錢的方法。
這三種方法中:
第一種可能影響到其它不需要打折的地方或者后面不打折了又要改回來,那么就需要反復修改源碼,不可行。
第二種直接修改接口,影響就太大了,每個實現類都被迫需要改動(當然如果是 JDK 1.8 之后的版本可以選擇新增?default?方法,這樣方法二就和第三種方法等價了)。
第三種方法貌似改動是最小的,但畢竟還是修改了源碼。
簡而言之,這三種方法都需要修改源碼,違背了開閉原則中的對修改關閉這一條。所以如果要遵循開閉原則,那么我們的做法應該是再新建一個蔬菜打折類來實現?IGoods?商品。
- 新建一個打折蔬菜類?DiscountCabbage.java。
- 最后讓我們新建一個測試類?TestOCP.java?來看看運行結果。
接下來需要執行?javac ocp/*.java?命令進行編譯。
最后再執行?java ocp.TestOCP?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才能更深入體會其中的思想)。
這樣就符合開閉原則了,而且后面有其它商品需要打折或者其它活動,都可以通過新建一個類來實現,擴展非常方便。
里氏替換原則
里氏替換原則:Liskov Substitution Principle,簡稱為?LSP。其指的是繼承必須確保超類所擁有的性質在子類中仍然成立,也就是說如果對每一個類型為 T1 的對象 o1 都有類型為 T2 的對象 o2,使得以 T1 所定義的程序 P 在所有的對象 o1 都替換成為 o2 時,程序 P 的行為沒有發生改變。
里氏替換原則具體可以總結為以下 4 點:
- 子類可以實現父類的抽象方法,但是不能覆蓋父類的非抽象方法。
- 子類中可以增加自己的特有方法。
- 當子類方法重載父類的方法時,方法的前置條件(即方法的輸入/入參)要比父類方法輸入的參數更寬松。
- 當子類實現父類的方法(重載/重寫/實現抽象方法),方法的后置條件(即方法的輸出/返回值)要比父類更嚴格或者相等。
我們以動物鳥類飛翔舉例進行說明。同樣的,這里需要新建一個?lsp?目錄,相關類創建在?lsp?目錄下。
- 新建一個鳥類?Bird.java。
- 再新建一個鷹類?Eagle.java?來繼承 Bird,并重寫其中的 fly 方法。
- 新建一個測試類?TestLSP.java?來測試。
接下來我們需要先執行?javac lsp/*.java?命令進行編譯。然后再執行?java lsp.TestLSP?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才會更能體會其中的思想)。
可以看到上面的例子中將父類替換成子類之后,fly?方法變成了在 8000 米高空飛翔(普通鳥類達不到),這就改變了父類的行為,導致替換不成立,所以這個例子就違背了里氏替換原則。
依賴倒置原則
依賴倒置原則:Dependence Inversion Principle,簡稱為?DIP。其指的是在設計代碼結構時,高層模塊不應該依賴低層模塊,而是都應該依賴其抽象。抽象不應該依賴細節,細節應該依賴抽象。通過依賴倒置原則可以減少類與類之間的耦合性,提高系統的穩定性,提高代碼的可讀性和可維護性,而且能夠降低修改程序所帶來的風險。
我們以超市出售商品舉例進行說明(同樣的,這里我們需要新建一個?dip?目錄,相關類創建在?dip?目錄下)。
- 假設一家超市剛開張,只有青菜賣,所以我們新建一個超市類?SuperMarket.java,里面只定義一個賣蔬菜的方法。
這個超市類直接和蔬菜綁定了,也就是依賴了具體的商品。假如要賣其它商品就需要修改源碼,違背了開閉原則,我們應該修改超市類依賴于抽象商品,而不能直接綁定具體商品。
- 新增一個商品接口?IGoods.java,接口中定義一個出售商品方法。
- 再新建一個蔬菜類?Cabbage.java?實現商品接口。
- 然后還需要編輯?SuperMarket.java?文件,將原先的超市類 SuperMarket 修改一下。
- 最后讓我們新建一個測試類?TestDIP.java?來測試結果。
接下來我們需要先執行?javac dip/*.java?命令進行編譯。然后再執行?java dip.TestDIP?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才會更能體會其中的思想)。
這時候如果需要新增其它商品,只需要新建一個具體商品類來實現?IGoods?接口,并作為參數傳入?sale?方法就可以了。
單一職責原則
單一職責原則:Single Responsibility Principle,簡稱為?SRP。其指的是不要存在多于一個導致類變更的原因。假如我們有一個類里面有兩個職責,一旦其中一個職責發生需求變更,那我們修改其中一個職責就有可能導致另一個職責出現問題,在這種情況應該把兩個職責放在兩個 Class 對象之中。
單一職責可以降低類的復雜度,提高類的可讀性和系統的可維護性,也降低了變更職責引發的風險。
我們以超市的進貨和出售舉例進行說明(同樣的,這里我們需要新建一個?srp?目錄,相關類創建在?srp?目錄下)。
- 新建一個商品類?Goods.java。
這個方法里面有兩個分支:進貨和售賣。也就是一個方法里面有兩個功能(職責),假如業務邏輯非常復雜,那么一個功能發生變化需要修改有很大的風險導致另一個功能也發生異常。所以為了符合單一職責原則我們應該進行如下改寫,將這兩個職責拆分成兩個類。
- 商品進貨類?BuyGoods.java。
- 商品售賣類?SaleGoods.java。
- 最后我們寫一個測試類?TestSRP.java?來對比一下兩種寫法。
接下來我們需要先執行?javac srp/*.java?命令進行編譯。然后再執行?java srp.TestSRP?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才會更能體會其中的思想)。
這樣對比之后大家應該一目了然,單一職責原則的兩個行為是兩個獨立的類,修改其中一個功能,就絕對不會導致另一個功能出現異常,符合了單一職責原則。
接口隔離原則
接口隔離原則:Interface Segregation Principle,簡稱為?ISP。接口隔離原則符合我們所說的高內聚低耦合的設計思想,從而使得類具有很好的可讀性、可擴展性和可維護性,在設計接口的時候應該注意以下三點:
- 一個類對其它類的依賴應建立在最小的接口之上。
- 建立單一的接口,不建立龐大臃腫的接口。
- 盡量細化接口,接口中的方法應適度。
我們以常見的動物的行為舉例進行說明(同樣的,這里我們需要新建一個?isp?目錄,相關類創建在?isp?目錄下)。
- 新建一個動物接口?IAnimal.java,定義三個方法:run,swim,fly。
- 新建一個 Dog 類?Dog.java?來實現 IAnimal 接口。
可以看到,fly 方法我什么也沒做,因為狗不會飛,但是因為 fly 方法和其它方法定義在了同一個接口里面,所以使得狗具備了不該具有的行為,這就屬于接口沒有隔離,我們應該把不同特征的行為進行隔離,即拆分成不同的接口。
- 新建一個接口?IFlyAnimal.java,只定義一個 fly 方法。
- 新建一個接口?IRunAnimal.java,只定義一個 run 方法。
- 新建一個接口?ISwimAnimal.java,只定義一個 swim 方法。
- 然后對上面的 Dog 類?Dog.java?進行改寫。
- 最后我們新建一個測試類?TestISP.java?來看一下運行效果。
接下來我們需要先執行?javac isp/*.java?命令進行編譯。然后再執行?java isp.TestISP?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才會更能體會其中的思想)。
這時候 Dog 需要什么行為就實現什么行為,不會再具有自己不該有的行為 fly 了。
迪米特法則(最少知道原則)
迪米特法則:Law of Demeter,簡稱為?LoD,又叫作最少知道原則(Least Knowledge Principle,LKP)。是指一個對象對其它對象應該保持最少的了解,盡量降低類與類之間的耦合。
我們以超市售賣青菜,老板和經理想知道賣出去了多少斤舉例進行說明(同樣的,這里我們需要新建一個?lod?目錄,相關類創建在?lod?目錄下)。
- 新建一個蔬菜商品類?Cabbage.java。
這時候經理和老板都需要知道商品出售情況,那么是不是經理和老板都需要直接和商品打交道呢?實際上不需要,按常理老板只需要向經理詢問就可以了,而經理直接和商品打交道就行了。
- 新建一個經理類?Manager.java。
可以看到經理類里面集成了具體的商品,然后調用了商品的方法獲得商品的出售記錄。
- 新建老板類?Boss.java。
- 新建一個測試類?TestLoD.java?來看看運行結果。
接下來我們需要先執行?javac lod/*.java?命令進行編譯。然后再執行?java lod.TestLoD?命令運行測試類(大家一定要自己動手運行哦,只有自己實際去運行了才會更能體會其中的思想)。
上面 Boss 類不會直接和商品打交道,而是通過經理去獲取想要的接口,這就是迪米特法則。不該知道的不要知道,我只要讓該知道的人知道就好了,你想知道那你就去找那個該知道的人。后面我們要介紹的中介者模式就是一種典型的遵守了迪米特法則的設計模式。
合成復用原則
合成復用原則:Composite Reuse Principle,簡稱為?CRP,又叫組合/聚合復用原則(Composition/Aggregate Reuse Principle,CARP)。指的是在軟件復用時,要盡量先使用組合(has-a)或者聚合(contains-a)等關聯關系來實現,這樣可以使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其它類造成的影響相對較少。
繼承通常也稱之為白箱復用,相當于把所有的實現細節都暴露給子類。組合/聚合也稱之為黑箱復用,對類以外的對象是無法獲取到實現細節的。
這個原則還是非常好理解的,像我們開發中經常用的依賴注入,其實就是組合,還有上面的迪米特法則中的經理類就是組合了商品類,所以在這里就不再單獨舉例子了。
寫在最后
設計模式是一種思想,而軟件設計七大原則就是設計思想的基石,設計模式之中可以處處看到這些設計原則,所以想要學好設計模式,那么這軟件設計的七大原則還是需要好好體會并理解,只有這樣,后面學習設計模式才會知其然更知其所以然。
點擊《Java 設計模式系統精講》進入學習頁面。
總結
以上是生活随笔為你收集整理的7种 Java 设计模式,你会几种?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微服务技术方案:Spring Cloud
- 下一篇: 高 star 开源项目来实验楼啦,深度学