本章比較三種解耦模式——ABSTRACT SERVERADAPTERBRIDGE——它們都用於在客戶端與伺服端之間建立間接層,但適用場景與設計取捨各不相同。

ABSTRACT SERVER 模式#

  • 最簡單的解耦方式:在客戶端與伺服端之間放置一個介面
  • 客戶端依賴介面而非具體類別,伺服端實作介面
  • 這就是 DIP 的直接應用——依賴反轉,讓依賴方向指向抽象

Switch 與 Light 的例子#

Figure 33.2: A bad way to extend Switch

  • 如果 Switch 直接依賴 Light,那麼要讓 Switch 控制其他設備(如 Motor)就必須修改 Switch——違反 OCP
  • 正確做法:定義 Switchable 介面,LightMotor 都實作它,Switch 只依賴 Switchable

補充: ABSTRACT SERVER 介面應歸屬於客戶端而非伺服端——這是 DIP 的關鍵。介面的命名與設計應反映客戶端的需求,而非伺服端的能力。

ADAPTER 模式#

  • 當伺服端的介面已經存在且無法修改時,ABSTRACT SERVER 模式不適用
  • ADAPTER 在已有的類別之上包裝一層,使其符合客戶端期望的介面

兩種形式#

Figure 33.4: Solving the table lamp problem with ADAPTER

Figure 33.5: Solving the table lamp problem with ADAPTER

  • 物件形式 ADAPTER:Adapter 持有被適配物件的參考,將呼叫轉發
  • 類別形式 ADAPTER:Adapter 同時繼承客戶端介面與伺服端類別(在 C# 中透過介面實作加上繼承)
// 物件形式
public class LightAdapter : Switchable
{
    private Light light;
    public LightAdapter(Light light) { this.light = light; }
    public void TurnOn() { light.TurnOn(); }
    public void TurnOff() { light.TurnOff(); }
}

Modem 問題#

Figure 33.7: Ideal solution to the modem problem

  • 系統有一個 Modem 介面(Dial, Hangup, Send, Receive),但有一種「專線 Modem」不需要撥號與掛斷
  • 如果讓專線 Modem 實作 DialHangup 為空操作,就違反了 LSP

Figure 33.9: Solving the modem problem with ADAPTER

  • 解法一:使用 ADAPTER,建立 DedicatedModemAdapter 實作 Modem 介面
  • 解法二:將 Modem 介面拆成兩個——Connection(Dial/Hangup)與 DataChannel(Send/Receive)

Figure 33.10: Solving the modem problem by merging type hierarchies

技巧: 選擇 ADAPTER 還是拆分介面,取決於實際情境。如果只有一兩個特例,ADAPTER 較為簡單;如果特例很多,重新設計介面階層可能更合適。

BRIDGE 模式#

Figure 33.11: BRIDGE solution to the modem problem

  • BRIDGE 用於處理兩個獨立變化維度的問題
  • 在 Modem 的例子中:一個維度是 Modem 的連線類型(撥號/專線),另一個維度是 Modem 的硬體實作(Hayes, USR, Ernie 等)
  • 如果不使用 BRIDGE,類別數量會爆炸式增長:DedicatedHayesModem, DialupHayesModem, DedicatedUSRModem, …
  • BRIDGE 將兩個維度分離成獨立的繼承階層,透過組合(而非繼承)連接:
    • 一側是 Modem(連線類型的抽象)
    • 另一側是 ModemImplementation(硬體實作的抽象)
    • 具體的連線類型持有具體的硬體實作參考

注意: BRIDGE 模式增加了相當的複雜度。只有在確實存在兩個獨立變化維度時才應使用它。如果只有一個維度在變化,ADAPTER 或 ABSTRACT SERVER 就足夠了。

本章小結#

  • ABSTRACT SERVER:最簡單的解耦——加一個介面。適用於你可以控制介面設計的情境
  • ADAPTER:在已有類別之上包裝介面。適用於無法修改伺服端的情境
  • BRIDGE:分離兩個獨立變化的維度。適用於型別組合會爆炸式增長的情境

三種模式的複雜度遞增,應從最簡單的開始考慮,只在需要時才升級到更複雜的方案。