概述#
本章以一個名為 Lexi 的 WYSIWYG 文件編輯器為案例,展示如何在實際應用中運用設計模式。Lexi 的介面包含文件編輯區、下拉選單、捲軸和頁面圖示,使用者可自由混合文字與圖形。
作者從 Lexi 的設計中提煉出 七個設計問題,每個問題都導向一個或多個設計模式的解決方案。這種「問題驅動」的教學方式,讓讀者理解模式不是憑空出現的抽象概念,而是為了解決具體設計難題而產生的。

Figure 2.1: Lexi's user interface
七大設計問題一覽#
| 設計問題 | 核心挑戰 | 對應模式 |
|---|---|---|
| 文件結構 | 統一處理簡單與複雜元素 | Composite |
| 格式化 | 可替換的排版演算法 | Strategy |
| UI 裝飾 | 動態添加邊框、捲軸等 | Decorator |
| 多種外觀風格 | 跨平台 Widget 建立 | Abstract Factory |
| 多種視窗系統 | 封裝視窗系統實作差異 | Bridge |
| 使用者操作 | 統一的請求封裝與 Undo/Redo | Command |
| 拼字檢查與斷字 | 不修改 Glyph 類別就能增加分析功能 | Iterator + Visitor |
文件結構 — Composite Pattern#
問題#
文件由字元、圖形、行、欄、頁等多層次元素組成。設計上需要:
- 維護文件的物理結構(文字與圖形排列成行、欄、表格等)
- 統一處理文字與圖形,不為任一方做特殊處理
- 統一處理單一元素與群組元素,支援任意複雜的巢狀結構
解法:Recursive Composition#
透過 Recursive Composition(遞迴組合) 的技巧,將簡單圖形元素組合成行,行組合成欄,欄組合成頁,以此類推。每個元素(無論可見或不可見的結構元素)都對應一個物件。

Figure 2.2: Recursive composition of text and graphics

Figure 2.3: Object structure for recursive composition of text and graphics
Glyph 抽象類別#
定義 Glyph 作為所有文件結構物件的抽象基底類別,子類別包含基本圖形元素(Character、Image)和結構元素(Row、Column)。
Glyph 的三大職責:
- 繪製自身 —
Draw(Window)操作 - 回報佔用空間 —
Bounds()回傳包圍矩形;Intersects()判斷點擊命中 - 管理子元素 —
Insert、Remove、Child、Parent操作

Figure 2.4: Partial Glyph class hierarchy
Composite Pattern 將遞迴組合的概念用物件導向的方式表達:讓葉節點和組合節點實作相同介面,客戶端無需區分處理。
格式化 — Strategy Pattern#
問題#
「表示」(representation)和「格式化」(formatting)是兩件事。有了文件結構後,還需要演算法來決定如何將 glyph 排列成行、欄。格式化演算法往往複雜且各有取捨:
- 快速但品質一般的簡單演算法
- 考慮「色彩」(文字與空白均勻分佈)的 TeX 演算法
- 以空間換取速度的快取策略
解法:Compositor 與 Composition#
將格式化演算法封裝在 Compositor 物件中,與文件結構解耦:
- Composition — 一種特殊的 Glyph,持有一個 Compositor 實例
- Compositor — 抽象類別,子類別實作不同的排版演算法(如
SimpleCompositor、TeXCompositor)

Figure 2.5: Composition and Compositor class relationships
運作流程:Composition 在需要格式化時呼叫 Compositor 的 Compose 操作,Compositor 遍歷 Composition 的子元素,插入 Row、Column 等結構性 glyph。

Figure 2.6: Object structure reflecting compositor-directed linebreaking
Strategy Pattern 的核心:將演算法封裝在物件中,讓策略(Compositor)與上下文(Composition)之間的介面夠通用,就能在不修改任何一方的前提下替換演算法,甚至可以在 執行時期動態切換。
UI 裝飾 — Decorator Pattern#
問題#
文件編輯區需要邊框和捲軸等裝飾。這些裝飾可能隨時增減,若用繼承實作會造成類別爆炸(BorderedComposition、ScrollableComposition、BorderedScrollableComposition…)。
解法:Transparent Enclosure#
透明封裝(Transparent Enclosure) 結合兩個概念:
- 單一子元素組合 — 裝飾物件包裹一個被裝飾的元件
- 相容介面 — 裝飾物件與被裝飾物件實作相同介面,客戶端無法區分
定義 MonoGlyph 抽象類別,預設將所有請求轉發給內含的元件。子類別如 Border 和 Scroller 覆寫特定操作,在轉發前後加入自己的行為(例如 Border::Draw 先讓元件繪製自身,再畫邊框)。

Figure 2.7: MonoGlyph class relationships
裝飾可以自由組合與排列。例如先用 Scroller 包裹 Composition,再用 Border 包裹 Scroller,順序不同會產生不同的視覺效果。

Figure 2.8: Embellished object structure
Decorator Pattern 將「裝飾」這個概念一般化:任何為物件添加職責的行為都算裝飾。相比繼承,它在執行時期動態組合,避免子類別膨脹。
多種外觀風格 — Abstract Factory Pattern#
問題#
Lexi 需要支援多種 Look-and-Feel 標準(Motif、Presentation Manager、Mac 等)。直接在程式碼中 new MotifScrollBar() 會造成:
- 無法在執行時期切換風格
- 移植時需逐一修改所有建構呼叫
- 遺漏一處就可能在 Mac 應用裡出現 Motif 選單
解法:抽象化物件建立過程#
定義 GUIFactory 抽象類別,提供 CreateScrollBar、CreateButton 等操作。每個具體子類別(MotifFactory、PMFactory、MacFactory)回傳對應風格的 Widget。

Figure 2.9: GUIFactory class hierarchy

Figure 2.10: Abstract product classes and concrete subclasses
客戶端透過 guiFactory 變數取得工廠實例,程式碼中完全不出現特定平台的類別名稱。工廠可在啟動時根據設定初始化,甚至可以重新初始化來動態切換整個 Widget 家族。
Abstract Factory Pattern 的重點在於「產品家族」:一次替換所有相關物件(按鈕、捲軸、選單等),確保它們風格一致。
多種視窗系統 — Bridge Pattern#
問題#
不同平台有不同的視窗系統(X Window、Presentation Manager、Macintosh),API 互不相容。與 Look-and-Feel 不同,這裡無法自行實作視窗系統,必須橋接到既有的平台 API。
解法:分離抽象與實作#
建立兩個獨立的類別階層:
- Window 階層 — 面向應用程式開發者的邏輯抽象(ApplicationWindow、IconWindow、DialogWindow)
- WindowImp 階層 — 封裝特定視窗系統的實作(XWindowImp、PMWindowImp)
Window 持有一個 WindowImp 的參照。當 Window 的操作被呼叫時,它將請求委託給 WindowImp,由後者轉換為平台特定的 API 呼叫。例如 DrawRect 在 X Window 下呼叫 XDrawRectangle,在 PM 下則使用多段路徑 API。
Window 與 WindowImp 的配對可透過 Abstract Factory 完成:WindowSystemFactory 負責建立正確的 WindowImp 子類別實例。
Bridge Pattern 讓兩個類別階層各自獨立演化。Window 階層可以新增子類別而不影響 WindowImp,反之亦然。關鍵在於:Window 的介面面向使用者需求,WindowImp 的介面面向系統能力,兩者的設計驅動力不同。
使用者操作 — Command Pattern#
問題#
使用者操作(建立文件、儲存、剪貼、字型變更等)散佈在各個物件中,且可能從不同介面觸發(選單、按鈕、快捷鍵)。需要:
- 統一機制存取分散的功能
- 不將操作綁定到特定 UI 元件
- 支援任意層級的 Undo/Redo
解法:將請求封裝為物件#
定義 Command 抽象類別,提供 Execute 操作。每個子類別封裝一種請求(FontCommand、PasteCommand、SaveCommand 等)。MenuItem 持有一個 Command 物件,使用者點擊時呼叫 Execute。

Figure 2.11: Partial Command class hierarchy

Figure 2.12: MenuItem-Command relationship
Undo/Redo 機制#
- 在 Command 介面加入
Unexecute— 反轉Execute的效果 - 加入
Reversible— 回傳布林值,判斷該命令是否可復原(例如無效的字型變更不需要 Undo) - 維護 Command History(命令歷史) — 一個命令列表加上「現在」指標,向左 Undo、向右 Redo,支援任意層級
Command Pattern 將請求參數化為物件,讓同一個操作可以從不同入口觸發,並自然地支援 Undo/Redo、命令佇列、命令日誌等進階功能。
拼字檢查與斷字 — Iterator + Visitor Pattern#
問題#
拼字檢查和斷字需要遍歷文件結構中分散的文字資訊,且未來可能新增搜尋、字數統計、文法檢查等分析功能。有兩個子問題:
- 如何存取散佈在不同資料結構中的資訊
- 如何執行分析而不必每次都修改 Glyph 類別
解法一:Iterator 封裝存取與遍歷#
將遍歷邏輯從 Glyph 中抽離,封裝在 Iterator 物件中:
- ArrayIterator、ListIterator — 對應不同底層資料結構
- PreorderIterator、PostorderIterator — 對應不同遍歷順序
每個 Glyph 提供 CreateIterator 操作,回傳適合自身資料結構的 Iterator。新增遍歷方式只需增加 Iterator 子類別,不必修改 Glyph。

Figure 2.13: Iterator class and subclasses
解法二:Visitor 封裝分析操作#
遍歷與分析應該分離,因為不同分析通常使用相同的遍歷方式。定義 Visitor 抽象類別,為每種 Glyph 子類別提供 Visit... 操作。Glyph 提供 Accept(Visitor&) 操作,內部呼叫 Visitor 上對應的方法(雙重分派)。
具體 Visitor 範例:
- SpellingCheckingVisitor — 累積字元組成單字,遇到非字母字元時檢查拼字
- HyphenationVisitor — 累積單字後計算斷字點,在適當位置插入 Discretionary glyph(行尾顯示連字號,行中則隱藏)
Visitor Pattern 適合類別結構穩定但分析操作頻繁新增的場景。新增分析只需增加 Visitor 子類別,不需修改任何 Glyph 類別。但反過來,若新增 Glyph 子類別,則所有 Visitor 都需更新。
本章總結#
Lexi 案例展示了八種設計模式如何協同運作,解決一個真實應用中的各種設計問題:
| 模式 | 解決的問題 | 核心手法 |
|---|---|---|
| Composite | 文件的階層結構表示 | 統一葉節點與組合節點介面 |
| Strategy | 可替換的格式化演算法 | 將演算法封裝在物件中 |
| Decorator | 動態添加 UI 裝飾 | 透明封裝 + 相容介面 |
| Abstract Factory | 跨平台 Widget 建立 | 抽象化物件建立過程 |
| Bridge | 跨視窗系統支援 | 分離抽象與實作階層 |
| Command | 統一操作介面與 Undo | 將請求封裝為物件 |
| Iterator | 統一遍歷不同資料結構 | 封裝存取與遍歷機制 |
| Visitor | 開放式分析功能擴充 | 雙重分派 + 分離操作與結構 |
這些模式並非文件編輯器特有,任何非平凡的應用程式都可能用到其中多種。反覆出現的設計原則是:封裝會變化的概念(encapsulate the concept that varies)。