凡研讀過 GoF《設計模式》一書的開發者,無不熟知 Decorator(裝飾器)模式。不妨暫時拋開既有認知,從日常開發經驗出發重新審視:裝飾器本質上是用於「裝飾某事物」的另一事物,它能直接改變被裝飾對象的職責或特徵,卻不改變其固有屬性。如相框裝飾畫作、化妝品修飾臉龐。
裝飾器主要有三種實現路徑:繼承、封裝、外掛。以下逐一剖析優劣與適用場景。
最直觀的方式:寫一個衍生類別繼承被裝飾類別,新增或覆寫方法賦予新職責。
public class DebugButton extends JButton {
public DebugButton() {
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("debug message");
}
});
}
}
ScrollOverButton 同理,改掛 MouseListener 動態切換邊框。BufferedInputStream 覆寫 read() 從緩衝區讀取。但 JScrollPane 若用繼承實現則極為複雜。
結論:繼承方式看似簡單,實則擴展困難、違背 OO 原則,不建議用於實現裝飾器。
核心思想:將被裝飾物件包裝在裝飾器內部,請求轉發給內部物件,轉發前後執行額外邏輯。
不建立新類別,而是利用被裝飾物件既有的回調機制(如 Listener)動態掛載行為。
public class DebugDecorator implements ActionListener {
public void decorateDebug(JButton butt) { butt.addActionListener(this); }
public void undecorateDebug(JButton butt) { butt.removeActionListener(this); }
public void actionPerformed(ActionEvent evt) {
JButton src = (JButton)evt.getSource();
System.out.println("debug message for button " + src);
}
}
使用時:
DebugDecorator decor = new DebugDecorator();
decor.decorateDebug(myButt); // 加裝
decor.undecorateDebug(myButt); // 卸載
同樣可實現 RollOverDecorator,兩者互不干擾,隨時增減。更可寫成單例(Singleton Decorator)供全系統共用,並擴充 decorateDebugContainer() 批量套用至容器內所有按鈕。
| 實現方式 | 優勢 | 劣勢 | 建議 |
|---|---|---|---|
| 繼承 | 保留型別、直觀 | 擴展性差、類別爆炸、違背 OO | 不推薦 |
| 封裝 | 符合 GoF 定義、短類別適用 | 長類別需大量轉發或犧牲型別 | 適用方法較少的類別(如 Stream) |
| 外掛 | 動態、零侵入、符合 OO、視覺元件效果佳 | 受限於被裝飾物件特徵、無法改核心繪製 | 首選視覺元件裝飾,視情況輔以封裝 |