1. 程式人生 > >Head First 設計模式 —— 14. 複合 (Compound) 模式

Head First 設計模式 —— 14. 複合 (Compound) 模式

#### 複合模式 在一個解決方案中結合兩個或多個模式,以解決一般或重複發生的問題。 `P500` #### 思考題 ```java public interface Quackable { public void quack(); } public class MallardDuck implements Quackable { public void quack() { System.out.println("Quack"); } } public class Goose { public void honk() { System.out.println("Honk"); } } ``` 假設我們想要在所有使用鴨子的地方使用鵝,畢竟鵝會叫、會飛、會遊,和鴨子差不多。什麼模式可以讓我們輕易地將鴨子和鵝摻雜在一起呢? `P503` - 介面卡模式。題目需要輕易地將一種行為轉換為另一種行為,且不要改變原有的類,所以需要使用介面卡轉換。 #### 思考題 我們要如何在不變化鴨子類的情況下,計算所有鴨子呱呱叫的總次數呢?有沒有什麼模式可以幫上忙?`P505` - 裝飾器模式。題目要求增加新的行為,且不改變原有類,所以可以使用裝飾器。 - 代理模式。代理模式會控制訪問,而鵝經介面卡後轉換的行為不應該被統計,所以可以通過代理模式進行控制。 #### 思考題 你能夠為鵝寫一個抽象工廠嗎?建立”內鵝外鴨“的物件時,你怎麼處理? `P511` - 新建一個工廠,專門建立被介面卡轉換成鴨子的鵝 ```java public abstract class AbstractGooseDuckFactory { public abstract Quackable createGooseDuck(); } public class GooseDuckFactory extends AbstractGooseDuckFactory { public Quackable createGooseDuck() { return new GooseAdapter(new Goose()); } } ``` #### 思考題 我們需要將鴨子視為一個集合,甚至是子集合(subcollection),如果我們下一次命令,就能讓整個集合的鴨子聽命行事,那就太好了。什麼模式可以幫我們? `P512` - 迭代器模式。由於我們需要將鴨子視為一個集合,可以遍歷執行同一操作,所以可以使用迭代器模式方便遍歷。 - 組合模式。由於鴨子集合可能會含有子集合和鴨子,並也需要支援上述行為,所以可以使用組合模式將鴨子和鴨子集合統一起來。 #### 思考題 你能夠有辦法持續追蹤個別鴨子的實時呱呱叫嗎? `P516` - 觀察者模式。題目意思就是鴨子在呱呱叫時通知觀察人員,所以鴨子是可被觀察的,應該繼承 `Observable` 類,而觀察人員應該實現 `Observer` 介面 ,觀察人員在個別鴨子上註冊以便實時接收鴨子的呱呱叫行為。 - 按照以上設計會修改所有的鴨子類,所以就想到可以再加一個裝飾器繼承 `Observable` 類,並實現 `Quackable` 介面,這樣改動量最小,不會改變原有鴨子類,也可以將鴨子和可被觀察解耦。但想象很美好,一去實現就會遇到很多問題:使用者程式碼必須與該裝飾器耦合,需要特判該裝飾器以執行註冊觀察者和通知觀察者的方法;該裝飾器只能最後包裝,如果被其他裝飾器包裝就無法再呼叫相應方法;不便於將相應的方法擴充套件到組合模式中的集合上。所以還是需要介面上的修改,改變所有鴨子的行為。 - 書上設計是讓 `Quackable` 介面繼承 `QuackObservable` 介面以便所有能叫的鴨子都能被觀察;修改所有鴨子類,並將 `Observable` 類組合進鴨子類中,將註冊觀察者和通知觀察者的方法內部委託到 `Observable` 相應的方法中;同時也要修改相應的裝飾器。 #### 思考題 我們還沒有改變一個 `Quackable` 的實現,即 `QuackCounter` 裝飾器。它也必須成為 `Observable` 。你何不試著寫出它的程式碼呢? `P518` ```java public class QuackCounter implements Quackable { Quackable duck; static int numberOfQuacks; public QuackCounter(Quackable duck) { this.duck = duck; } public void quack() { duck.quack(); ++numberOfQuacks; } public static int getQuacks() { return numberOfQuacks; } public void registerObserver(Observer observer) { duck.registerObserver(observer); } public void notifyObservers() { duck.notifyObservers(); } } ``` #### 思考題 萬一呱呱叫學家想觀察整個群,又該怎麼辦呢?當觀察某個組合時,就等於觀察組合內的每個東西。 `P520` ```java public class Flock implements Quackable { ArrayList ducks = new ArrayList(); public void add(Quackable duck) { ducks.add(duck); } public void quack() { Iterator iterator = ducks.iterator(); while(iterator.hasNext()) { Quackable duck = (Quackable) iterator.next(); duck.quack(); } } public void registerObserver(Observer observer) { Iterator iterator = ducks.iterator(); while(iterator.hasNext()) { Quackable duck = (Quackable) iterator.next(); duck.registerObserver(observer); } } public void notifyObservers() { // 鴨群註冊觀察者都委託到孩子上了,所以通知觀察者的事情並不需要鴨群做任何事 } } ``` #### 所思所想 - 可以通過讓原有介面繼承新介面的方式,再增加介面方法和相應的功能的同時,減少使用者修改程式碼。例如:JDK7 中就讓原有的 `Closable` 介面繼承 `AutoClosable` 介面,使得原有的使用者程式碼都不必修改就能在 JDK7中使用帶資源的 `try` 語句能自動關閉資源的新特性。(第一次看見 `AutoClosable` 介面時,直接從語義上就認為 `AutoClosable` 繼承了 `Closable` ,沒想到正相反) > 本文首發於公眾號:滿賦諸機([點選檢視原文](https://mp.weixin.qq.com/s/RTdYuc_FR19OPAnBaMpr4A)) 開源在 GitHub :[reading-notes/head-first-design-patterns](https://github.com/idealism-xxm/reading-notes/tree/master/head-first-design-patterns) ![](https://user-images.githubusercontent.com/16055078/103480735-02019200-4e11-11eb-91a2-70a687781