Adapter Façade Decorator設計模式在分類上屬於結構模式。結構模式描述怎樣將類和對象結合起來形成一個更大的結構。
將類和對象結合起來形成一個更大的結構,這裡就有一個耦合的問題,如果類和對象是非常穩定的,耦合到什麼程度都是沒有問題的。問題還是歸結到變化上,如果發生變化強耦合的後果就是修改壓力會沿著依賴鏈條傳遞下去,就像多米諾骨牌一樣引起大範圍的坍塌。
從結構模式的描述中我們可以看出耦合是難以避免的,我們要做的就是在這個必然的前提下做出努力,EVP原則(封裝變化原則)提示我們封裝變化隔離變化,目標是:靈活的耦合;
什麼樣的耦合是靈活的耦合?我們經常這樣做:耦合發生在兩個具體類(可以執行個體化)之間,一個類持有另一個類的直接引用,這就是具體耦合(Concrete Coupling)。顯然這中情況下 類和類之間的依賴是很強的,變化發生波及的範圍也是最廣的。靈活的耦合就是抽象耦合(Abstract Coupling),抽象耦合中耦合關係發生在一個具體類和一個抽象之間(抽象類別或者介面)。
仔細觀察抽象耦合,我們看到了DIP(依賴倒置)原則,依賴倒置原則告訴我們應該依賴於抽象,應該面向介面編程而非實現。DIP強調的就是系統內實體之間的關係靈活性。DIP不是孤立的,應該想到OCP是DIP的目標,LSP是DIP的基礎,DIP關鍵在抽象。
結構模式中的耦合是抽象耦合,而且從定義的描述中我們知道還要處理類的靈活耦合和對象的靈活耦合。怎麼做到的呢?
類的結構模式是使用繼承機製做到,繼承也是封裝變化的方法,當一個類從父類繼承並實現某一個介面時,這個新的類就把父類的結構和介面的結構結合起來,類的結構模式是靜態。
對象的結構模式是使用組合機制來實現,是動態。
下面我們以Adapter模式為例,進行分析:
Adapter
應用Adapter模式的動機就是轉化不相容的介面。
Adapter模式最常見的用途就是保持多態性。在各種設計模式組合起來使用的時候,Adapter常用來保持其它設計模式所需要的多態。有了Adapter模式我們進行設計時就不必擔心原有類的介面了:只要一個類在概念上完成了所需的功能,那麼我們就可以使用Adapter模式為它提供正確的介面。
對象適配器和類適配器的實現使用不同的機制:前者使用組合後者使用繼承。
類適配器使用繼承,概念上的適配器和被適配者實際上是一個類,這個類可以方便的使用父類已經實現的方法並進行覆蓋改寫(override)。同樣由於類適配器使用了繼承,所以只能使用特定的被適配類,適配器類Adapter和特定的被適配器類Adaptee綁定,所以這裡就沒有對象適配器靈活了。如果要適配目標類的所有子類,那麼類適配器就不能勝任了。
我們來看一下對象適配器:
對象適配器使用對象組合,根據LSP(裡氏替換)原則,對象適配器可以適配被適配類及其子類。對象適配器將工作委託給被適配者,Client和介面綁定而不是具體實現,它面向介面而非實現,很好的符合了DIP(依賴倒置原則)。
Adapter模式實現的工作量是由介面的規模決定的,一個巨大的介面上實現Adapter也是一件痛苦的事情。我們期待一個良好的介面,介面設計的原則最重要的就是ISP原則(介面隔離原則)。
再一次描述一下ISP原則:多個專門的介面比唯一的總介面要好。
介面的設計問題最容易由介面使用者發現。舉一個生活中的例子,無論你使用移動還是聯通,定製的服務都是“套餐”,套餐裡面好多服務都是必選的。對於很多不會使用簡訊的群體(比如老年人),簡訊包月的必選服務就很霸王,對於消費者是一個負擔。一個肥胖的介面同樣對於使用者也是負擔。
ISP原則拒絕向Client提供不需要的行為,一個類對另外一個類的依賴是建立在最小的介面上。這其實符合另外一個OO設計原則:
Least Knowledge Principle(LKP) 最少知識原則,另外一個名稱是Law of Demeter Lod迪米特法則是指:一個對象應當對其他的對象有最少的瞭解。如果其中一個類需要調用另外一個類的某一個方法的話,可以通過第三者來轉寄這個調用。這個第三者實際上是一個封裝類,它的作用就是調用轉寄(Call Forwarding)完成溝通。
如果要找一個符合Lod原則的典型,那就是Facade模式了:
Facade
Facade面板模式定義一個高層介面使得子系統更容易使用。我們使用面板模式的動機是簡化。
引入Facade將子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性和可移植性。 當你需要構建一個階層的子系統時,使用Facade模式定義子系統中每層的進入點。如果子系統之間是相互依賴的,你可以讓它們僅通過Facade進行通訊,從而簡化了它們之間的依賴關係。
雖然Facade並沒有“屏蔽子系統”--子系統的完整介面還是暴露出來了。我們可以強制Facade成為Client與子系統互動的唯一入口,一方面它完成了Client與系統的解耦,另一方面它所處的特殊位置可以做一寫跟蹤工作,比如特定方法的調用。
我個人認為這種強制是有必要的,它保證了Facade更嚴格得遵守了Lod原則。
甚至我們可以用抽象類別實現Facade而它的具體子類對應於不同的子系統實現,這可以進一步降低客戶與子系統的耦合度。
Decorator
裝飾者使命是添加行為,這也是我們使用Decorator模式的動機。
適配器的作用是介面轉換,Decorator裝飾者模式絕對不允許修改介面,如果原來的對象介面發生變化,它所有的裝飾類都要修改匹配它的變化。
為什麼不允許Decorator修改介面?我們要保證已經正常的程式不受到影響,我們做的只是擴充!如果使用繼承派生子類會影響對象的內部,而一個Decorator指揮影響對象的外表。Decorator動態給對象增加新的職責而又不影響其他對象。
簡單總結:
- 繼承也是封裝變化的方法
- 抽象耦合是靈活的耦合
- 介面設計要符合ISP原則
- Facade很好的符合了LKP原則
- 個人觀點應該強制Facade成為子系統唯一進入點
- Decorator模式不允許修改介面