今天要介紹的是 Decorator 模式,這也是 GoF 提出的模式之一。
在軟體開發時,有時會遇到一個類別需要在執行階段時增加額外的功能,但同時又希望讓原類別盡量少被修改,以便於維護和擴展。
當系統功能隨時間增加而逐漸複雜化時,直接修改原有類別結構與功能會讓系統難以理解與維護,且直接修改現有類別也可能影響原有功能,而讓原先可正常運作的功能出現非預期錯誤,或者讓單一類別承擔過多責任,該如何在不影響原有類別功能、不修改底層程式碼的情況下,動態增加額外功能?
Decorator (裝飾器)模式是一種促進程式碼重用的模式,裝飾器可以向系統現有類別動態添加功能,會稱為「裝飾(decoration)」是因為它帶來的功能本身不是類別必備的基本功能,而是在需要該功能時動態加入。
在前端應用中,很常需要使用按鈕元件,但按鈕又需要有不同類型和樣式,此時我們可先建立一個按鈕的基本元件:
class Button {
constructor() {
// 傳入預設的文字和顏色
this.text = "Click me!";
this.color = "red";
}
render() {
return `<button style="color: ${this.color}">${this.text}</button>`;
}
getStyle() {
return this.color;
}
getText() {
return this.text;
}
}
接著我們需要建立多個裝飾器來裝飾按鈕物件,在建立裝飾器之前我們先建立裝飾器的基本 class,之後的裝飾器都會 extends
這個裝飾器基本 class,避免直接 extends
Button
這個 class,基礎裝飾器 class 可以為多個裝飾器提供共通接口和基本行為,所有裝飾器都可繼承它,能確保裝飾器間的一致性,也避免直接改到 Button
的基本行為,以下為基本裝飾器類別:
class ButtonDecorator extends Button {
constructor(button) {
super();
this.button = button;
}
render() {
return this.button.render();
}
}
再來就可因應需求來建立多個裝飾器:
// 為按鈕增加 icon 的裝飾器
class IconButtonDecorator extends ButtonDecorator {
constructor(button, icon) {
super(button);
this.icon = icon;
}
render() {
const originalRender = this.button.render(); // 先得到基本按鈕的 render 結果
// 用字串 replace 的方式改變基本的 render 結果
return originalRender.replace(/<\/button>$/, ` <span>${this.icon}</span></button>`);
}
}
// 為按鈕改變顏色的裝飾器
class ColorButtonDecorator extends ButtonDecorator {
constructor(button, color) {
super(button);
this.newColor = color;
}
render() {
const originalRender = this.button.render();
// 更新字串中的 style屬性
return originalRender.replace(/style="[^"]*"/, `style="color: ${this.newColor}"`);
}
}
在基本情境下,我們可使用 Button
元件即可,但如果需要 icon 或要改變顏色,就可透過裝飾器來疊加改變按鈕的屬性,同時維持基本 Button
class 不變,以下為使用範例:
// 首先先初始化按鈕
let myButton = new Button();
// 添加裝飾
myButton = new IconButtonDecorator(myButton, '🌟');
myButton = new ColorButtonDecorator(myButton, 'blue');
console.log(myButton.render());
// 會印出 <button style="color: blue">Click me! <span>🌟</span></button>
這裡舉例的 Decorator 模式比較簡單,網路上可以看到很多複雜的應用範例,但我想說以簡單範例來了解此模式的核心理念就好,如果有興趣的讀者可以再看看 Reference 的文章~