設計原則(Java 與模式-筆記 一)

 

第二部分 面向對象的設計原則

 

如何同時提高一個軟件系統的可維護性Maintainability)和可複用性Reuseability)是面向對象的設計要解決的核心問題。

一個好的系統設計應該有如下的性質:可擴展性(Extensibility)、靈活性(Flexibility)、可插入性(Pluggability

系統的可擴展性是由“開-閉”原則、里氏代換原則、依賴倒轉原則和組合/聚合複用原則所保證的。

恰當地提高系統的可複用性,可以提高系統的靈活性。在一個設計得當的系統中,每一個模塊都相對於其他模塊獨立存在,並只保持與其他模塊的儘可能少的通信。這樣一來,在其中某一個模塊發生代碼修改的時候,這個修改的壓力不會傳遞到其他的模塊。

系統的靈活性是由“開-閉”原則、迪米特法則、接口隔離原則所保證的。

恰當地提高系統的可複用性,可以提高系統的可插入性。在一個符合“開-閉”原則的系統中,抽象層封裝了與商業邏輯有關的重要行爲,這些行爲的具體實現由實現層給出。當一個實現類不再滿足需要,需要以另一個實現類取代的時候,系統的設計可以保證舊的類可以被“拔出(Unplug)”,新的類可以被“插入(Plug)”。

系統的可插入性是由“開-閉”原則、里氏代換原則、依賴倒轉原則和組合/聚合複用原則所保證的。

 

1、“開-閉原則”(Open-Closed Principle,縮寫爲OCP

“開-閉”原則:一個軟件實體應當對擴展開放,對修改關閉

如何實現?

1)抽象化是關鍵

Java 語言裏,可以給出一個或多個抽象 Java 類或Java 接口,規定出所有的具體類必須提供的方法的特徵作爲系統設計的抽象層。這個抽象層預見了所有的可能擴展,因此,在任何擴展情況下都不會改變。這就使得系統的抽象層不需要修改,從而滿足了“開-閉”原則的第二條:對修改關閉。

同時,由於從抽象層導出一個或多個新的具體類可以改變系統的行爲,因此係統的設計對擴展時開發的,這就滿足了“開-閉”原則的第一條:對擴展開放。

2)對可變性的封裝原則

找到一個系統的可變因素,將它封裝起來。一種可變性不應當散落在代碼的很多角落裏,而應當被封裝到一個對象裏面。一種可變性不應當與另一種可變性呼喝在一起。所有的類圖的繼承接口一般不會超過兩層,不然就意味着將兩種不同的可變性混合在了一起。

相關的設計模式:策略模式、簡單工廠模式、工廠方法模式、抽象工廠模式、建造模式、橋樑模式、門面模式、調停者模式、訪問者模式迭代子模式。

當讀者學習設計模式的時候,要學會問一個問題:這個設計模式可以對什麼樣的變換開放,以及它做到這一點所付出的代價是什麼。通過這樣的思考,可以更加透徹地瞭解這種模式對“開-閉”原則的支持程度,以及這種設計模式本身。

當代碼包含大段大段的代碼轉移語句塊往往意味着某種可變性。可以將這種可變性用多態代替,就意味着將這種可變性封裝起來。但是如果一個條件轉移語句沒有涉及重要的商務邏輯,或者不糊隨着時間的變化而變化,也不意味着任何的可擴展性,那麼它就沒有涉及任何有意義的可變性,這時候運用多態性就是毫無意義的。

 

附加專題內容:

Java 接口的常見的用法:單方法接口、標識接口、常量接口

在設計中,只要有可能,不要從具體類繼承。從繼承的等級結構裏面,樹葉節點均應當是具體類,而樹枝節點均應當是抽象類(或接口)。這樣的設計是所有的Java 設計師都應當努力做到的。

繼承使用的條件,當以下的條件全部滿足時:

1)子類是超類的一個特殊種類,而不是超類的一個角色,也就是要區分“Has-A”與“Is-A”兩種關係的不同。Has-A關係應當使用聚合關係描述,而只有Is-A關係才符合繼承關係。

2)永遠不會出現需要將子類換成另一個類的子類的情況。如果設計師不是很肯定一個類會不會在將來變成另一個類的子類的話,就不應當將這個類設計成當前這個超類的子類。

3)子類具有擴展超類的責任,而不是具有置換掉(Override)或者註銷掉(Nullify)超類的責任。如果子類需要大量地置換掉超類的行爲,那麼這個子類不應當成爲這個超類的子類。

4)只有在分類學角度上有意義時,纔可以使用繼承,不要從工具類繼承。

 

2、里氏代換原則(Liskov Substitution Principle,縮寫爲LSP

里氏代碼原則的嚴格表達是:如果對每一個類型爲T1的對象o1,都有類型爲T2的對象o2,使得以T1定義的所有程序P在所有的對象o1都代換成o2時,程序P的行爲沒有變化,那麼類型T2是類型T1的子類型。換而言之,一個軟件實體如果使用的是一個基類的話,那麼一定適用於其子類,而且它根本不能察覺出基類對象和子類對象的區別

里氏代換原則講的是基類與子類的關係。如果有兩個具體類A之間的關係違反了這一原則的設計,根據具體情況可以在下面的兩種重構方案中選擇一種。

 



 相關的設計模式
:策略模式、合成模式、代理模式。

 

3、依賴倒轉原則(Dependence Inversion Principle,縮寫爲DIP

依賴倒轉原則講的是:要依賴於抽象,不要依賴於具體。也就是說抽象不應當依賴於細節;細節應當依賴於抽象。另一種表述是,要針對接口編程,不要針對實現編程。

下圖是違反依賴倒轉原則的設計:

 



 正確的設計應該是:

 



 相關設計模式
:工廠方法模式、模板方法模式、迭代子模式。

依賴倒轉原則的缺點是因爲依賴倒轉的緣故,對象的創建很可能要使用對象工廠,以比秒對具體類的直接引用,此原則的使用還會導致大量的類。

 

4、接口隔離原則(Interface Segreation Principle,縮寫爲ISP

接口隔離原則講的是:使用多個專門的接口比使用單個的總接口要好。換而言之,從一個客戶類的角度來講,一個類對另外一個類的依賴性應當是建立在最小的接口上的。

接口隔離原則的一般常用的有:角色的合理劃分、定製服務

 

5、合成/聚合複用原則(Composite/Aggregate Reuse Principle,縮寫爲CARP

合成/聚合複用原則就是:在一個新的對象裏面使用一些已有的對對象,使之成爲新對象的一部分,新的對象通過向這些對象的委派達到複用已有功能的目的。另一種表述是:儘量使用合成/聚合,儘量不要使用繼承

合成和聚合的區別:聚合用來表示“擁有”關係或者整體與部分的關係;而合成則用來表示一種強得多的“擁有”關係,在合成關係裏,部分和整體的生命週期是一樣的。

 

6、迪米特法則(Law of Demeter,縮寫爲LoD

迪米特法則又叫做最少知識原則,就是說,一個對象應當對其他對象有儘可能少的瞭解

狹義的迪米特法則:如果兩個類不必彼此直接通信,那麼這兩個類就不應當發生直接的相互作用。如果其中的一個類需要調用另一個類的某一個方法的話,可以通過第三者轉發這個調用。

下圖是一個不滿足迪米特法則的系統:

 



 滿足地卡特法則的系統應該爲:

 



 上圖中使用了Friend 類作爲中間層,間接調用Strange 類的方法。這樣使得調用的具體細節被隱藏在Friend內部,從而使SomeoneStrange 之間的直接聯繫被省略掉了。這樣一來,使得系統內部的耦合度降低。

但這樣做有個明顯的缺點:會在系統裏造出大量的小方法,散落在系統的各個角落。這些方法僅僅是傳遞間接的調用。

廣義的迪米特法則:其實,迪米特法則所談論的,就是對對象之間的信息流量、流向以及信息的影響的控制。

在軟件系統中,一個模塊設計得好與不好的最主要、最重要的標誌,就是該模塊在多大的程度上將自己的內部數據和其他與實現有關的細節隱藏起來。一個設計得好的模塊可以將它所有的實現細節隱藏起來,徹底地將提供給外界的API和自己的實現分隔開來,而不理會模塊內部的工作細節。這一概念就是“信息的隱藏”,或者叫封裝

迪米特法則的主要用意是控制信息的過載。在將迪米特法則運用到系統設計中時,要注意下面的幾點:

1)在類的劃分上,應當創建有弱耦合的類。類之間的耦合越弱,就越有利於複用。一個處在弱耦合中的類一旦被修改,不會對有關係的類造成波及。

2)在類的結構設計上,每一個類都應當儘量降低成員的訪問權限。換言之,一個類包裝好各自的private 狀態。這樣一來,想要了解其中的一個類的意義時,不需要了解更多別的類的細節。一個類不應當public 自己的屬性,而應當提供取值和賦值方法讓外界間接方法自己的屬性。

3)在類的設計上,只要有可能,一個類應當設計成不變類

4)在對其他類的引用上,一個對象對其對象的引用應當降低到最低

相關設計模式:門面模式、調停者模式。

 

   這篇總結是我在一年前從 Java 與模式書上摘錄的,自己覺得有用,容易忘記的,有時間便記錄總結下。發表在這主要是爲了方便以後能更好的回顧,還有幾篇也將陸續整理下發表。。by zhxing

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章