一、面向對象設計七大原則
單一職責原則(Single Responsibility Principle)
每一個類應該專注于做一件事情。里氏替換原則(Liskov Substitution Principle)
超類存在的地方,子類是可以替換的。依賴倒置原則(Dependence Inversion Principle)
實現盡量依賴抽象,不依賴具體實現。接口隔離原則(Interface Segregation Principle)
應當為客戶端提供盡可能小的單獨的接口,而不是提供大的總的接口。迪米特法則(Law Of Demeter)
又叫最少知識原則,一個軟件實體應當盡可能少的與其他實體發(fā)生相互作用。開閉原則(Open Close Principle)
面向擴展開放,面向修改關閉。組合/聚合復用原則(Composite/Aggregate Reuse Principle CARP)
盡量使用合成/聚合達到復用,盡量少用繼承。原則: 一個類中有另一個類的對象。
二、細則
1) 單一職責原則(Single Responsibility Principle)
- 因為:
可以降低類的復雜度,一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單的多;提高類的可讀性,提高系統(tǒng)的可維護性;變更引起的風險降低,變更是必然的,如果單一職責原則遵守的好,當修改一個功能時,可以顯著降低對其他功能的影響。需要說明的一點是單一職責原則不只是面向對象編程思想所特有的,只要是模塊化的程序設計,都適用單一職責原則。 - 所以:
從大局上看Android中的Paint和Canvas等類都遵守單一職責原則,Paint和Canvas各司其職。
2)里氏替換原則(Liskov Substitution Principle)
- 因為:
里氏替換原則告訴我們,在軟件中將一個基類對象替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,如果一個軟件實體使用的是一個子類對象的話,那么它不一定能夠使用基類對象。里氏替換原則是實現開閉原則的重要方式之一,由于使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來對對象進行定義,而在運行時再確定其子類類型,用子類對象來替換父類對象。 - 所以:
使用里氏替換原則時需要注意,子類的所有方法必須在父類中聲明,或子類必須實現父類中聲明的所有方法。盡量把父類設計為抽象類或者接口,讓子類繼承父類或實現父接口,并實現在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統(tǒng)的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現。
從大局看Java的多態(tài)就屬于這個原則。
3)依賴倒置原則(Dependence Inversion Principle)
- 因為:
具體依賴抽象,上層依賴下層。假設B是較A低的模塊,但B需要使用到A的功能,這個時候,B不應當直接使用A中的具體類;而應當由B定義一抽象接口,并由A來實現這個抽象接口,B只使用這個抽象接口;這樣就達到了依賴倒置的目的,B也解除了對A的依賴,反過來是A依賴于B定義的抽象接口。通過上層模塊難以避免依賴下層模塊,假如B也直接依賴A的實現,那么就可能造成循環(huán)依賴。 - 所以:
采用依賴倒置原則可以減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性,減少并行開發(fā)引起的風險,提高代碼的可讀性和可維護性。
從大局看Java的多態(tài)就屬于這個原則。
4)接口隔離原則(Interface Segregation Principle)
- 因為:
提供盡可能小的單獨接口,而不要提供大的總接口。暴露行為讓后面的實現類知道的越少越好。譬如類ProgramMonkey通過接口CodeInterface依賴類CodeC,類ProgramMaster通過接口CodeInterface依賴類CodeAndroid,如果接口CodeInterface對于類ProgramMonkey和類CodeC來說不是最小接口,則類CodeC和類CodeAndroid必須去實現他們不需要的方法。將臃腫的接口CodeInterface拆分為獨立的幾個接口,類ProgramMonkey和類ProgramMaster分別與他們需要的接口建立依賴關系。也就是采用接口隔離原則。 - 所以:
建立單一接口,不要建立龐大的接口,盡量細化接口,接口中的方法盡量少。也就是要為各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。依賴幾個專用的接口要比依賴一個綜合的接口更靈活。接口是設計時對外部設定的約定,通過分散定義多個接口,可以預防外來變更的擴散,提高系統(tǒng)的靈活性和可維護性。
從大局來說Java的接口可以實現多繼承就是接口隔離原則的基礎保障。
5)迪米特法則(Law Of Demeter)
- 因為:
類與類之間的關系越密切,耦合度也就越來越大,只有盡量降低類與類之間的耦合才符合設計模式;對于被依賴的類來說,無論邏輯多復雜都要盡量封裝在類的內部;每個對象都會與其他對象有耦合關系,我們稱出現成員變量、方法參數、方法返回值中的類為直接的耦合依賴,而出現在局部變量中的類則不是直接耦合依賴,也就是說,不是直接耦合依賴的類最好不要作為局部變量的形式出現在類的內部。 - 所以:
一個對象對另一個對象知道的越少越好,即一個軟件實體應當盡可能少的與其他實體發(fā)生相互作用,在一個類里能少用多少其他類就少用多少,尤其是局部變量的依賴類,能省略盡量省略。同時如果兩個類不必彼此直接通信,那么這兩個類就不應當發(fā)生直接的相互作用。如果其中一個類需要調用另一個類的某一方法的話,可以通過第三者轉發(fā)這個調用。
從大局來說Android App開發(fā)中的多Fragment與依賴的Activity間交互通信遵守了這一法則。
6)開閉原則(Open Close Principle)
- 因為:
開放封閉原則主要體現在對擴展開放、對修改封閉,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。軟件需求總是變化的,世界上沒有一個軟件的是不變的,因此對軟件設計人員來說,必須在不需要對原有系統(tǒng)進行修改的情況下,實現靈活的系統(tǒng)擴展。 - 所以:
可以通過Template Method模式和Strategy模式進行重構,實現對修改封閉,對擴展開放的設計思路。
封裝變化,是實現開放封閉原則的重要手段,對于經常發(fā)生變化的狀態(tài),一般將其封裝為一個抽象,拒絕濫用抽象,只將經常變化的部分進行抽象。
7)組合/聚合復用原則(Composite/Aggregate Reuse Principle CARP)
- 因為:
其實整個設計模式就是在講如何類與類之間的組合/聚合。在一個新的對象里面通過關聯關系(包括組合關系和聚合關系)使用一些已有的對象,使之成為新對象的一部分,新對象通過委派調用已有對象的方法達到復用其已有功能的目的。也就是,要盡量使用類的合成復用,盡量不要使用繼承。
如果為了復用,便使用繼承的方式將兩個不相干的類聯系在一起,違反里氏代換原則,哪是生搬硬套,忽略了繼承了缺點。繼承復用破壞數據封裝性,將基類的實現細節(jié)全部暴露給了派生類,基類的內部細節(jié)常常對派生類是透明的,白箱復用;雖然簡單,但不安全,不能在程序的運行過程中隨便改變;基類的實現發(fā)生了改變,派生類的實現也不得不改變;從基類繼承而來的派生類是靜態(tài)的,不可能在運行時間內發(fā)生改變,因此沒有足夠的靈活性。 - 所以:
組合/聚合復用原則可以使系統(tǒng)更加靈活,類與類之間的耦合度降低,一個類的變化對其他類造成的影響相對較少,因此一般首選使用組合/聚合來實現復用;其次才考慮繼承,在使用繼承時,需要嚴格遵循里氏代換原則,有效使用繼承會有助于對問題的理解,降低復雜度,而濫用繼承反而會增加系統(tǒng)構建和維護的難度以及系統(tǒng)的復雜度,因此需要慎重使用繼承復用。