Head First 設計模式 —— 14. 複合 (Compound) 模式
- 2021 年 1 月 17 日
- 筆記
- Head First 設計模式, 設計模式
複合模式
在一個解決方案中結合兩個或多個模式,以解決一般或重複發生的問題。 P500
思考題
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
-
新建一個工廠,專門創建被適配器轉換成鴨子的鵝
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
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
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
,沒想到正相反)
本文首發於公眾號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/head-first-design-patterns