1. 程式人生 > >面向物件七大原則詳解

面向物件七大原則詳解

單一職責原則    定義:一個類只負責一個領域的相應職責。 開閉原則 定義:軟體實體應對擴充套件開放,而對修改關閉。 里氏替換原則   定義:所有引用基類的物件能夠透明的使用其子類的物件。 依賴倒轉原則   定義:抽象不應該依賴於細節,細節依賴於抽象。 介面隔離原則   定義:使用多個專門的介面,而不是使用單一總介面。 合成複用原則   定義:儘量使用物件組合,而不是繼承來達到複合目的。 迪米特法則   定義:一個軟體實體應當儘可能少的與其它實體發生相互作用。


1.單一職責原則 每個型別(包括介面和抽象)功能要求單一,只對外負責一件事情,應該僅有一個原因引起類的變更。不要讓一個類存在多個改變的理由。只應該做和一個任務相關的業務,不應該把過多的業務放在一個類中完成。單一職責原則不只是面向物件程式設計思想所特有的,只要是模組化的程式設計,都適用單一職責原則。
原因:這也是靈活的前提,類被修改的機率很大,因此應該專注於單一的功能。如果你把多個功能放在同一個類中,功能之間就形成了關聯,改變其中一個功能,有可能中止另一個功能,這時就需要新一輪的測試來避免可能出現的問題。 核心
:拆分到最小單位,解決複用和組合問題,封裝的優良體現,即解耦和增強內聚性(高內聚,低耦合)。 優點: 降低了類的複雜度,明確了對應的職責、可讀性和維護性變高、如果介面單一職責做得好,修改介面影響的僅僅是相應的實現類。

2.開放封閉原則 一個軟體實體應該對擴充套件開發,對修改關閉。即在設計一個模組的時候,應當使這個模組可以在不被修改的前提下被擴充套件。開閉原則是設計原則的核心原則,其他的設計原則都是開閉原則表現和補充。實現開閉原則的方法就是抽象。 
原因:軟體系統的功能上的可擴充套件性要求模組是擴充套件開放的,軟體系統的功能上的穩定性,持續性要求是修改關閉的。根本控制需求變動風險,縮小維護成本。 核心:
用抽象構建框架,用實現類實現擴充套件,在不修改原有模組的基礎上能擴充套件其功能。 優點: 增加穩定性、可擴充套件性高。  

3.替換原則(里氏代換原則) 子類能夠替換父類,出現在父類能夠出現的任何地方,子類必須完全實現父類的方法。在類中呼叫其他類是務必要使用父類或介面,如果不能使用父類或介面,則說明類的設計已經違背了LSP原則。覆蓋或實現父類的方法時輸入引數可以被放大。即子類可以過載父類的方法,但輸入引數應比父類方法中的大,這樣在子類代替父類的時候,呼叫的仍然是父類的方法。里氏替換原則是針對繼承而言的,如果繼承是為了實現程式碼重用,也就是為了共享方法,那麼共享的方法應該保持不變,不被子類重新定義。如果繼承是為了多型那麼,而多型的前提是子類覆蓋父類的方法所以將父類定義為抽象類,抽象類不能夠例項化物件也就不存在替換這一說。
原因:這是多型的前提 要保證父類的方法不被覆蓋。 核心:里氏替換原則是實現開閉原則的重要方式之一,當使用繼承時,儘量遵循替換原則,儘量不要去重寫或者過載父類的方法,以免破壞整個繼承體系的 。因為父類在定義或者實現某些方法時,規定了必須遵守的規則和契約。儘量不要從可例項化的父類中進行繼承,而是繼承基於抽象類和介面。 優點:總感覺只要是面向物件這個原則是預設遵守的。  

4.依賴倒轉原則 具體依賴抽象,上層依賴下層。兩個模組之間依賴的應該是抽象(介面或抽象類)而不是細節(實現類)。細節(實現類)依賴於抽象(介面或抽象類)。相對於實現類的多變性,抽象的東西要穩定得多,基於抽象的構架也比基於實現的架構更加穩定,且擴充套件性更高。通過建構函式、setter方法傳遞依賴物件,介面宣告實現依賴物件。要根據介面隔離原則分拆介面時,必須滿足單一職責原則。想要理解依賴倒置原則,必須先理解傳統的解決方案。面相物件的初期的程式,被呼叫者依賴於呼叫者。也就是呼叫者決定被呼叫者有什麼方法,有什麼樣的實現方式,這種結構在需求變更的時候,會付出很大的代價,甚至推翻重寫。依賴倒置原則就是要求呼叫者和被呼叫者都依賴抽象,這樣兩者沒有直接的關聯和接觸,在變動的時候,一方的變動不會影響另一方的變動。
原因:依賴抽象的介面可以適應變化的需求。防止需求變化時對被依賴者的改變過大。 核心:要依賴於抽象,面向抽象程式設計,不要依賴於具體的實現,思想是面向介面程式設計。高層模組不應該依賴低層模組,兩者都應該依賴其抽象(抽象類或介面)。解耦呼叫和被呼叫者。 優點:採用依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,減少並行開發引起的風險,提高程式碼的可讀性和可維護性。從大局看Java的多型就屬於這個原則。  
5.介面隔離原則 模組間要通過具體介面分離開,而不是通過類強耦合。一個介面不需要提供太多的行為,一個介面應該只提供一種對外的功能,不應該把所有的操作都封裝到一個介面當中。分離介面的兩種實現方法:使用委託分離介面和使用多重繼承分離介面。例如A類對B類的依賴,可以抽象介面I,B實現I,A類依賴I來實現。但是抽象介面必須功能最小化(與單一功能原則有點不謀而合)。建立單一介面,不要建立龐大的介面,儘量細化介面,介面中的方法儘量少。也就是要為各個類建立專用的介面,而不要試圖去建立一個很龐大的介面供所有依賴它的類去呼叫。依賴幾個專用的介面要比依賴一個綜合的介面更靈活。介面是設計時對外部設定的約定,通過分散定義多個介面,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
原因:是單一職責的必要手段,儘量使用職能單一的介面,而不使用職能複雜、全面的介面。介面是為了讓子類實現的,如果想達到職能單一那麼介面也必須滿足職能單一。如果介面融合了多個不相關的方法,那子類就被迫實現所有方法,儘管有些方法是根本用不到的。這就是介面汙染。 核心:拆分,從介面開始。不應該強迫客戶程式依賴他們不需要使用的介面,一個類對另一個類的依賴應該建立在最小的介面上。 使用專門的介面,比用統一的介面要好。 優點:降低耦合性、提升程式碼可讀性、影藏實現細節。  
6.合成複用原則:複用的種類: 繼承、合成聚合,在複用時應優先考慮使用合成聚合而不是繼承。儘量使用物件組合,而不是繼承來達到複用的目的。該原則就是在一個新的物件裡面使用一些已有的物件,使之成為新物件的一部分:新的物件通過向這些物件的委派達到複用已有功能的目的。為了達到程式碼複用的目的,儘量使用組合與聚合,而不是繼承。組合聚合只是引用其他的類的方法,而不會受引用的類的繼承而改變血統。
原因:繼承的耦合性更大,比如一個父類後來新增實現一個介面或者去掉一個介面,那子類可能會遭到毀滅性的編譯錯誤,但如果只是組合聚合,只是引用類的方法,就不會有這種巨大的風險,同時也實現了複用。 核心:多使用聚合/組合達到程式碼的重用,少使用繼承複用。  優點:組合/聚合複用原則可以使系統更加靈活,類與類之間的耦合度降低,一個類的變化對其他類造成的影響相對較少,因此一般首選使用組合/聚合來實現複用。

7.迪米特原則 最小依賴原則又叫最少知識原則,一個類對其他類儘可能少的瞭解。在模組之間只通過介面來通訊,而不理會模組的內部工作原理,可以使各個模組的耦合成都降到最低,促進軟體的複用在類的劃分上,應該建立有弱耦合的類;在類的結構設計上,每一個類都應當儘量降低成員的訪問許可權;在類的設計上,只要有可能,一個類應當設計成不變;在對其他類的引用上,一個物件對其它物件的引用應當降到最低;儘量降低類的訪問許可權;謹慎使用序列化功能;不要暴露類成員,而應該提供相應的訪問器(屬性)。要求類之間的直接聯絡儘量的少,兩個類的訪問,通過第三個中介類來實現。每個物件都會與其他物件有耦合關係,出現成員變數、方法引數、方法返回值中的類為直接的耦合依賴,而出現在區域性變數中的類則不是直接耦合依賴,也就是說不是直接耦合依賴的類最好不要作為區域性變數的形式出現在類的內部。
原因:一個類如果暴露太多私用的方法和欄位,會讓呼叫者很茫然。並且會給類造成不必要的判斷程式碼。所以,我們使用盡量低的訪問修飾符,讓外界不知道我們的內部。這也是面向物件的基本思路。這是迪米特原則的一個特性,無法瞭解類更多的私有資訊。 核心:一個物件應當對其他物件有儘可能少的瞭解,軟體實體應當儘可能少的與其他實體發生相互作用。意思就是降低各個物件之間的耦合,要求儘量的封裝,儘量的獨立,儘量的使用低級別的訪問修飾符以提高系統的可維護性。 優點:降低耦合度、增加穩定性。  
1) 高內聚、低耦合和單一職能的“衝突”:實際上,這兩者是一回事。內聚,要求一個類把所有相關的方法放在一起,初看是職能多,但有個“高”,就是要求把聯絡非常緊密的功能放在一起,從整體看是一個職能的才能放在一起,所以兩者是不同的表述而已。   2)多個單一職能介面的靈活性和宣告型別問題:如果一個類實現多個介面,那麼這個類應該用哪個介面型別宣告呢?應該是用一個抽象類來繼承多個介面,而實現類來繼承這個介面。宣告的時候,型別是抽象類。   3)最少知識原則和中介類氾濫兩種極端情況:這是另一種設計的失誤。迪米特原則要求類之間要用中介來通訊,但類多了以後,會造成中介類氾濫的情況,這種情況,我們可以考慮中介模式,用一個總的中介類來實現。當然,設計模式都有自己的缺陷,迪米特原則也不是十全十美,互動類非常繁多的情況下,要適當的犧牲設計原則。   4)繼承和組合聚合複用原則的“衝突”:繼承也能實現複用,那這個原則是不是要拋棄繼承了?不是的。繼承更注重的是“血統”,也就是什麼型別的。而組合聚合更注重的是借用“技能”。並且,組合聚合中,兩個類是部分與整體的關係,組合聚合可以由多個類的技能組成。這個原則不是告訴我們不用繼承了都用組合聚合,而是在“複用”這個點上,我們優先使用組合聚合。  
總結: 所以可以看出前輩們給定我們這些原則,實際上是為了1、降低耦合度;2、提高穩定性;3、增加可讀和可維護性;4、提高擴充套件性。同時發現,上面的原則的根本就是讓我們儘量在定義好核心類之後用相應的介面去實現核心類的其他方法,在引入時儘量引入介面。所以面向物件變成的精髓之一就是面向介面程式設計。