一、介紹
在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名為 Design Patterns - Elements of Reusable Object-Oriented Software(中文譯名:設計模式 - 可復用的面向對象軟件元素) 的書,該書首次提到了軟件開發中設計模式的概念,四位作者合稱 GOF(全拼 Gang of Four),簡稱四人幫!
書中共說到 23 種設計模式,這些模式可以分為三大類:創建型模式(Creational Patterns)、結構型模式(Structural Patterns)、行為型模式(Behavioral Patterns)。
當然,隨著軟件的快速發展,還出現另一類我們所熟知的設計模式:J2EE 設計模式。
首先要跟大家說明的是,設計模式其實不是一項新技術,而是眾多軟件開發人員經過相當長的一段時間的試用和試錯總結出來的一套軟件設計開發實踐方案,學習這些知識有助于經驗不足的開發人員通過一種簡單快捷的方式來學習軟件設計。
也不BB了,下面我們就一起來深入了解一下大神們總結的這套方法論!
二、方法論
2.1、單一職責原則
單一職責原則,顧名思義,一個類負責一個功能的處理,比如我們在代碼中經常會涉及到調用第三方的接口,通常我們往往會將一個接口請求方法封裝成一個http請求的工具類,這樣其他的調用方能非常方便的進行調用。
單一職責原則,主要的目的是將一個功能劃分到合適的粒度,讓這些各自執行單一職責的類,各司其職。
還有我們常常說的mvc模式,將數據操作與業務邏輯處理進行分離,也是屬于單一職責原則的一種。
2.2、開閉原則
開閉原則,指的是:對擴展開放,對修改關閉。
這里的意思是在增加新功能的時候,能不改代碼就盡量不要改,如果只增加代碼就完成了新功能,那是最好的。
下面我舉個簡單的例子,大家可能就懂了。
在實際的項目開發過程中,尤其是與第三方公司進行接口對接的時候,例如 a 公司,他的接口協議要求采用rsa加密;b 公司,他的接口協議采用aes加密,c公司,他的接口協議要求采用md5就可以了...
在一開始的時候,可能你沒有想那么多,設想所有其他公司跟你對接的時候,全部采用rsa加密,那么當第二家公司來了時候,要求采用aes,這個時候,你必須得改代碼才能支持這種功能,假如又來了其他公司呢?
在這種情況下,我們可以采用面向接口編程的思想,定義一個抽象的方法,然后針對不同的加密算法,編寫不同的實現類,當新來了一家公司要求采用不同的加密算法時,我們只需要擴展一個新的實現類,在邏輯處理層進行路由即可快速完成開發和對接!
實現開閉原則的主要步驟就是將業務方法里面的邏輯進行抽象化,從而實現邏輯層面代碼的解耦。
2.3、里氏代換原則
里氏替換原則,通俗的說就是:子類可以擴展父類的功能,但不能改變父類原有的功能。
也就是說:子類繼承父類時,除添加新的方法完成新增功能外,盡量不要重寫父類的方法。
當子類在重寫父類的方法時,如果使用不當,很可能會改變父類方法中的邏輯語義,進而造成邏輯處理混亂。
在實際的業務開發中,尤其是大家在使用類繼承操作時,應當謹慎重新父類方法,方法定義的時候,名稱也盡量不要發生重名。
同時,應該注意類過多的繼承,例如 A 類繼承自 B 類,B 類 繼承自 C 類,C 類繼承 D 類,這種場景下,第一:邏輯很可能非常復雜,代碼可讀性可能很差,第二種可能會出現方法名或者屬性名沖突。
我曾經修改一個老項目 bug 的時候,就出現過這種情況,一個實體類出現三層關系的繼承,當我把子類進行序列化成json的時候,突然報錯,原因就是因為里面有個屬性,在基類里面也定義了,但是類型卻不一樣,也就是說在這個子類里面有兩個相同的屬性,只是字段類型不同而已,從而導致這個序列化報錯。
因此,大家在使用類繼承操作時,一定要謹慎處理,最好的情況下是一層關系繼承,這樣即使出現問題了,也好排查,范圍可控;繼承關系多了,反而代碼變的非常復雜,每個方法邏輯都需要一一梳理清楚,你才敢去下手,這種情況下,反而為難了自己!
2.4、依賴倒轉原則
依賴倒轉原則,簡單的說就是面向接口編程,依賴于抽象而不依賴于具體,他是實現開閉原則的一個途徑。
依賴倒轉原則主要有以下幾點:
- 高層的模塊不應該依賴于低層的模塊,這兩者都應該依賴于其抽象
- 抽象不應該依賴細節
- 細節應該依賴抽象
換句話說,就是將具體類里面的方法進行邏輯抽象化,提升到接口或者抽象類里面,由具體的實現類去實現具體的業務邏輯,抽象層不關注細節。
也就是我們俗稱的,不管遇到啥事情,先看大局,在看小點。
舉個例子,例如我們常常使用的MVC框架,在Service層通常會先編寫接口類,然后在編寫服務實現類,這就是典型的面向接口編程,這種方案有哪些好處呢?
- 可以減少研發人員并行開發引起的風險
- 可以顯著提高代碼的可讀性和可維護性
- 可以降低類間的耦合性
當一個大項目開發的時候,試想一下,如果2個以上研發人員在編寫同一個類的同一個方法,沒有定義接口的情況下,隨著時間的流逝,這個類大概率會非常混亂,同時,里面的方法估計閱讀起來也很困難,一眼望去基本上不知道這個方法是干嘛的,尤其是重載方法很多的時候,尤其困難。
當我們引入接口之后,將所有的核心方法全部抽象定義,當我們對其進行二次開發的時,也會能快速定位到以前的和諧方法,然后進行快速定位和維護。
面向接口編程,還有一個很大的好處,就是可以降低類之間的耦合度。
2.5、接口隔離原則
接口隔離原則的意思是:盡量將一個接口拆的更細,使用多個隔離的接口,比使用單個接口要好。
這個原則基本上是從大型軟件架構出發、便于升級和維護的思想誕生出來的。
比如,一個第三方保險服務系統里面,如果是小項目,你可以在一個接口里面定義投保方法、取消方法、 退保方法、 理賠方法、 保單方法這5種方法。
當你只跟一家保險公司合作的時候,這套接口定義是沒問題的,但是假如現在公司的業務發展的非常快,已經引入10幾家保險公司了,你會發現這個接口基本上頂不住了,在此有兩種方法可以解決這種技術難題:
- 1、針對不同的保險公司,又重新定義一個接口,然后還是里面的5種方法
- 2、針對不同的操作行為,比如投保,單獨定義一套接口,然后針對不同的保險公司編寫不同的實現類
這兩種其實本質都是將一個接口,拆成多個顆粒度更細的接口,然后從行為上,讓各種的實現類更加獨立,進而降低類之間的耦合度,開發起來層次清晰,有利于系統的穩定!
2.6、組合/聚合復用原則
合成復用原則的意思就是:盡量使用組合/聚合的方式,而不是使用繼承。
所謂組合/聚合的方式,其實就是我們在springMVC里面使用的ioc依賴注入方式,
- @Component
- public class AService {
- /**
- * ioc依賴注入
- */
- @Autowired
- private BService bService;
- //業務代碼...
- }
AService類調用BService類,有兩種方式可以實現,第一種就是通過繼承方式,可以直接調用;第二種方式就是通過ioc依賴注入的方式,在類里面進行組合操作,然后進行調用。
很明顯,第二種方式,我們使用的愛不釋手!
這種方式有個好處就是,代碼層次清晰,編寫優雅,用上之后容易上癮,推薦使用!
2.7、迪米特法則
迪米特法則,又稱最少知道原則,意思就是說:一個實體應當盡量少地與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
類似單一原則的思想,例如我們編寫實體類的時候,基本上就是屬性名稱,加get/set。
- public class User {
- /**
- * 用戶id
- */
- private Long userId;
- /**
- * 用戶姓名
- */
- private String userName;
- public Long getUserId(){
- return this.userId;
- }
- public void setUserId(Long userId){
- this.userId = userId;
- }
- public String getUserName(){
- return this.userName;
- }
- public void setUserName(String userName){
- this.userName = userName;
- }
- }
當一個實體里面出現跟自己沒太大關系的代碼時,例如數據運算邏輯處理,這個時候應當將這部分代碼全部清理出去,交由業務處理層來出來。
從實體類的定義來說,它主要的功能就是承擔數據展示,當多個業務處理層在使用這個實體類的時候,如果你們嵌套了很多的數據運算邏輯,可能在返回的時候,客戶端得到的實體類的屬性值,并不是正確的結果!
三、小結
上面一共介紹了 7 種設計原則,基本上都是大神們經過很多次血的教訓,總結出來的一套方法論。
這些設計原則,可以給同行在實際的軟件設計過程中,提供一些思路,具體的應用,還需要結合實際的業務場景進一步思考,怎么讓系統變的更加可靠,開發更佳迅速,代碼閱讀起來更佳輕松,關鍵在于靈活運用!
原文鏈接:https://mp.weixin.qq.com/s/3dF8cy9LWh5A0oGWL2p35g