本章深入探討 VISITOR 家族的設計模式,包括傳統的 VISITOR、ACYCLIC VISITOR、DECORATOR 與 EXTENSION OBJECT。這些模式都解決同一個問題:如何在不修改既有類別階層的情況下為其增加新行為。
傳統 VISITOR 模式#
意圖與結構#

Figure 35.1: Modem hierarchy
- 問題情境:有一個穩定的類別階層(如
Modem的各種實作),需要在不修改這些類別的前提下增加新操作 - VISITOR 使用雙重派發(Double Dispatch):階層中的每個類別都有一個
Accept(Visitor)方法,Visitor 對每種類別都有一個對應的Visit方法
public interface ModemVisitor
{
void Visit(HayesModem modem);
void Visit(ZoomModem modem);
void Visit(ErnieModem modem);
}
public class HayesModem : Modem
{
public void Accept(ModemVisitor visitor)
{
visitor.Visit(this); // 雙重派發
}
}
public class UnixModemConfigurator : ModemVisitor
{
public void Visit(HayesModem m) { /* Hayes 的 Unix 設定 */ }
public void Visit(ZoomModem m) { /* Zoom 的 Unix 設定 */ }
public void Visit(ErnieModem m) { /* Ernie 的 Unix 設定 */ }
}- 優點:新增操作(新的 Visitor)很容易——只需新增一個類別
- 缺點:新增類別階層中的型別很困難——所有的 Visitor 都必須修改
注意: 傳統 VISITOR 在 Visitor 介面與被訪問的類別之間建立了循環依賴——Visitor 依賴所有具體類別,而所有具體類別也依賴 Visitor 介面。這限制了系統的可擴展性。
ACYCLIC VISITOR(非循環訪問者)#
- 為了打破循環依賴,ACYCLIC VISITOR 使用空的基底 Visitor 介面加上針對每個型別的退化介面(degenerate interface)
public interface ModemVisitor { } // 空介面
public interface HayesModemVisitor
{
void Visit(HayesModem modem);
}
public class HayesModem : Modem
{
public void Accept(ModemVisitor visitor)
{
if (visitor is HayesModemVisitor v)
v.Visit(this);
}
}- 優點:打破了循環依賴,新增型別不需要修改所有 Visitor
- 缺點:需要型別轉換(cast),執行時有輕微的效能損失
DECORATOR 模式#
- DECORATOR 透過包裝既有物件來增加行為,而非修改類別本身
- 與 VISITOR 不同,DECORATOR 不需要雙重派發——它直接實作相同的介面
public class LoggingModem : Modem
{
private Modem innerModem;
public LoggingModem(Modem modem)
{
this.innerModem = modem;
}
public void Dial(string phoneNumber)
{
Log("Dialing " + phoneNumber);
innerModem.Dial(phoneNumber);
}
// 其餘方法同理
}- 優點:簡單、透明(客戶端不知道 Decorator 的存在)
- 缺點:只能在既有方法的前後增加行為,無法增加全新的方法
EXTENSION OBJECT 模式#

Figure 35.4: Structure of bill of materials report generator
- 當需要為物件增加全新的介面(不只是裝飾既有方法)時,EXTENSION OBJECT 最為合適
- 每個物件可以持有一個 Extension 的字典,客戶端透過名稱取得特定的 Extension
public class Part
{
private Dictionary<string, Extension> extensions =
new Dictionary<string, Extension>();
public Extension GetExtension(string name)
{
extensions.TryGetValue(name, out Extension ext);
return ext;
}
public void AddExtension(string name, Extension extension)
{
extensions[name] = extension;
}
}
public interface Extension { }
public interface BomExtension : Extension
{
string GetPartCount();
string GetDescription();
}- BOM(Bill of Materials)報表範例:每個
Part可以有一個BomExtension,報表產生器透過GetExtension("BOM")取得並使用它 - Assembly(組合件)與 PiecePart(零件)的 BomExtension 實作各不相同
技巧: EXTENSION OBJECT 特別適合外掛系統——物件本身不知道會有哪些 Extension,Extension 在運行時動態加入。這提供了極高的擴展靈活性。
模式比較#
| 模式 | 適用場景 | 優勢 | 代價 |
|---|---|---|---|
| VISITOR | 類別階層穩定,操作頻繁變化 | 集中新操作的邏輯 | 循環依賴,新增型別困難 |
| ACYCLIC VISITOR | 類別階層與操作都可能變化 | 打破循環依賴 | 型別轉換的效能成本 |
| DECORATOR | 在既有方法前後增加行為 | 透明包裝 | 無法增加新介面 |
| EXTENSION OBJECT | 為物件增加全新功能 | 最大擴展靈活性 | 較高複雜度 |
重點: 這四種模式都在不修改既有類別的情況下增加新行為——這是 OCP(開放封閉原則)的具體實踐。選擇哪一種取決於「什麼在變化」以及「變化的頻率與方向」。
本章小結#
VISITOR 家族的模式提供了在封閉的類別階層上增加開放行為的多種策略。傳統 VISITOR 透過雙重派發集中操作邏輯;ACYCLIC VISITOR 打破循環依賴;DECORATOR 透明地包裝行為;EXTENSION OBJECT 動態地擴展物件能力。理解這些模式的取捨,才能在設計時做出正確的選擇。