Head First 設計模式 —— 14. 複合 (Compound) 模式
阿新 • • 發佈:2021-01-17
#### 複合模式
在一個解決方案中結合兩個或多個模式,以解決一般或重複發生的問題。 `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