30分鐘掌握物件導向類的設計原則
看過設計模式的人不少,但看過Martin的物件導向的設計原則的估計不多(詳情可參考《敏捷式軟體開發 (Agile Software Development):原則、模式與實踐》)。實際上這兩者是相輔相成的:設計模式是具體的實踐方法,而設計原則是指導思想;設計模式讓你知道How,而設計原則讓你知道Why。
《敏捷式軟體開發 (Agile Software Development):原則、模式與實踐》原著洋洋洒洒幾十萬言,介紹物件導向類的類的設計幾個原則也有幾十頁,沒有耐心的朋友估計看不下去。沒關係,這裡我給大家一個精簡版的,讓你讀完本博就能夠初步掌握這些原則,而且附送一些疑難解答,讓你更加容易理解。
1 SRP(單一職責原則)
這個原則看起來很簡單,就是說一個類只能承擔一個職責。
但這裡有一個關鍵:“職責”是如何理解的?
按照漢語的理解,職責其實分為兩部分:“職”和“責”。“職”就是這個類是什麼,而“責”就是這個類要幹什麼。
舉個例子來說:Door是一個對象,那麼它的“職”就是門,“責”就是“開門、關門”等;而Lock的“職”就是鎖,“責”就是“上鎖、開鎖”。如果設計的時候Door同時具有鎖的職責,那麼Door就違反了SRP原則。
2 OCP(開閉原則)
相信這是大家見得最多的原則,而且很多人都是這麼解釋的“對擴充開放、對修改封閉”,更加有人總結為“不修改代碼增加新的功能”!
太神奇了,不修改代碼增加新的功能!但我不免疑惑:不修改代碼怎麼增加新的功能呢?難道代碼會像生物一樣,基因變異?
仔細研究過後才發現,原來是這些總結的人誤導了我,根本不是什麼“不修改代碼增加新的功能”,也不是那個省略了主語的“對擴充開放,對修改封閉”,而是“被調用者開放擴充,調用者封閉修改”
還是舉門的例子:Door對象是被其它對象例如人People調用,那麼Door就是被調用者,People就是調用者。Door對象可以擴充為“防盜門”、“防火門”、“逃生門”等,但People在調用的時候不需要關注具體是什麼門,只需要調用這些門公用的“開門、關門”等操作即可。
3 LSP (Liskov替換原則)
這個看起來是比較難理解的原則,但我可以給一個很容易理解的總結:“子類的輸入不能比父類多,子類的輸出不能比父類少!”。
“輸入”就是指調用類的時候要給出的條件,最常見的就是函數參數,而一般的語言都可以從文法上保證這種子類和父類相同函數的參數必須相同;還有另外一種隱性的條件即“假設”或者“要求”也必須相同。
舉個簡單的例子:長方形和正方形。按照數學的定義,正方形是特殊的長方形。依照此定義看起來好像可以將正方形定義為長方形的子類,但實際上在物件導向設計中則是不行的,因為按照設定長方形長寬的方法不能來設定正方形的邊長,正方形要求長寬必須相等,而長方形沒有此“要求”。這就是子類的“假設”或者“假設”多於父類,違反了LSP原則。
“輸出”就是指調用類後返回的結果。即:子類的返回結果要包含父類的返回結果,可以多但絕對不能少。
為什麼設計時要滿足LSP原則呢?其實很簡單,因為調用者看到的只有父類,它根本不知道到底是哪個子類,調用者所有的處理都是基於父類提供給調用者的資訊(包括輸入資訊和輸出資訊)。
4 DIP(依賴反轉原則)
這個原則看起來有點嚇人,換個簡單的說法你就明白了,其實它就對應於《設計模式》開頭提到的兩個原則中的一個:“基於介面編程,而不是基於實現編程”。(另外一個是什麼呢?)
這裡的“編程”包括“調用者”和“被調用者”的編程。“調用者”基於介面進行調用,“被調用者”基於介面進行具體實現。
注意:和“依賴反轉”類似的兩個比較容易混淆的概念是“依賴注入”和“控制反轉”,詳細可以參考如下博文:http://blog.csdn.net/taijianyu/archive/2008/04/28/2338311.aspx
5 ISP(介面隔離原則)
這個原則也很簡單,就是說一個對象不要提供多個介面,不同的介面應該分離到不同的對象上去。
雖然大師的水平不容懷疑,但這裡我還是要懷疑一下:SRP和ISP本質上應該是一個原則,只是說法不一樣而已。
為什麼這麼說呢?大家可以看看SRP原則,它說的是“單一職責”。那麼職責最終體現在對象上是什麼呢?對,不就是介面麼!也就是說,如果遵循了SRP原則,介面自然就隔離了。
================================================
怎麼樣,看到這裡是否只花了30分鐘?那麼這些原則你是否基本都掌握了呢?如果還有什麼疑問,歡迎留言討論。