iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
自我挑戰組

一條龍的軟體開發到維護,從校園工讀到職場工程師系列 第 15

Day15-Code design我遇到比較常見的patten/裝飾者模式 (Decorator Pattern)

  • 分享至 

  • xImage
  •  

今天來講解 裝飾者模式 (Decorator Pattern) CSS就很像裝飾者

裝飾者模式的核心思想是:在不改變原有物件結構的情況下,動態地、漸進式地為一個物件增加額外的功能或職責。

核心思想與 CSS 的類比

裝飾者模式的核心思想是:在不改變原有物件結構的情況下,動態地、漸進式地為一個物件增加額外的功能或職責。

它就像一層層地把功能「包」上去,而不是透過繼承來產生一個功能更強大的子類別。

現在,我們來看看 CSS:

  1. 原始物件 (Component):
    在網頁中,一個最單純的 HTML 元素,例如 <div><p>,就是我們的原始物件。它有自己最基本的功能和樣貌。

    <div>這是一個基本的區塊</div>
    
  2. 裝飾者 (Decorators):
    CSS 的每一條規則,例如 border, background-color, padding, box-shadow,都可以看作是一個「裝飾者」。每一條規則都為原始的 <div> 增加了一項新的「外觀職責」。

  3. 動態組合 (Dynamic Composition):
    最關鍵的地方來了:你可以自由地、動態地將這些 CSS 規則(裝飾者)組合起來應用到 <div>(原始物件)上。

    .my-box {
        border: 1px solid black; /* 裝飾者A:加上邊框 */
        background-color: #f0f0f0; /* 裝飾者B:加上背景色 */
        box-shadow: 5px 5px 10px #888; /* 裝飾者C:加上陰影 */
    }
    

    你不需要為了一個「有邊框和背景色的 div」去發明一個新的 HTML 標籤。你只需要將這些裝飾動態地「附加」到原始物件上即可。這種方式遠比繼承(例如,創造一個 <DivWithBorderAndBackground> 類別)要靈活得多。

這個比喻完美地詮釋了裝飾者模式的精髓:保持原始物件的純粹,並透過外部包裝來擴充功能。


解決什麼問題?

裝飾者模式主要用來解決**「類別爆炸 (Class Explosion)」**的問題。

假設我們在做一個飲料店系統。飲料有紅茶、綠茶、咖啡。調味料有牛奶、糖、珍珠。

如果使用繼承,你需要為每種組合都建立一個子類別:

  • 紅茶
  • 加牛奶的紅茶
  • 加糖的紅茶
  • 加牛奶和糖的紅茶
  • 加牛奶和糖和珍珠的紅茶
  • ...

才兩種飲料、三種調味料,組合就已經多到難以管理了。這就是「類別爆炸」。裝飾者模式透過「組合」優雅地解決了這個問題。


程式碼實作 (Java 飲料店範例)

1. 統一介面 (Component Interface)

首先,定義一個所有飲料和調味料(裝飾者)都必須遵守的共同介面 Beverage

// Component: 所有被裝飾者和裝飾者都必須實現的介面
public interface Beverage {
    String getDescription();
    double cost();
}

2. 原始物件 (Concrete Components)

這是我們的基礎飲料,例如濃縮咖啡 Espresso

// Concrete Component: 基礎的、被裝飾的物件
public class Espresso implements Beverage {
    public String getDescription() {
        return "濃縮咖啡";
    }

    public double cost() {
        return 60.0; // 價格
    }
}

3. 抽象裝飾者 (Abstract Decorator)

這是模式的關鍵之一。它是一個抽象類別,它實現Beverage 介面,但同時內部又持有一個 Beverage 物件的參考。這就是「包裝」的體現。

// Abstract Decorator: 所有調味料裝飾者的父類別
public abstract class CondimentDecorator implements Beverage {
    // 透過組合,持有一個被裝飾的 Beverage 物件
    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    // 將 getDescription 的職責委派給被裝飾者
    public String getDescription() {
        return beverage.getDescription();
    }
}

4. 具體裝飾者 (Concrete Decorators)

現在我們可以建立具體的調味料,例如「牛奶 (Milk)」。它繼承自 CondimentDecorator

// Concrete Decorator: 牛奶
public class Milk extends CondimentDecorator {

    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        // 在被裝飾者原有的描述基礎上,加上自己的描述
        return beverage.getDescription() + ", 加牛奶";
    }

    @Override
    public double cost() {
        // 在被裝飾者原有的價格基礎上,加上自己的價格
        return beverage.cost() + 15.0;
    }
}

// Concrete Decorator: 摩卡
public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        super(beverage);
    }



    @Override
    public String getDescription() {
        return beverage.getDescription() + ", 加摩卡";
    }

    @Override
    public double cost() {
        return beverage.cost() + 20.0;
    }
}

5. 客戶端使用

來看看客戶端如何像堆積木一樣,自由組合出一杯客製化飲料。

public class CoffeeShop {
    public static void main(String[] args) {
        // 點一杯最基本的濃縮咖啡
        Beverage beverage1 = new Espresso();
        System.out.println("飲料1: " + beverage1.getDescription() + " NT$" + beverage1.cost());

        System.out.println("---------------------------------");

        // 現在,我們想做一杯「雙份摩卡加牛奶的濃縮咖啡」
        // 1. 先從一杯濃縮咖啡開始
        Beverage beverage2 = new Espresso();
        // 2. 用 Milk 包裝它
        beverage2 = new Milk(beverage2);
        // 3. 用 Mocha 包裝它
        beverage2 = new Mocha(beverage2);
        // 4. 再用 Mocha 包裝一次(雙份摩卡)
        beverage2 = new Mocha(beverage2);

        System.out.println("飲料2: " + beverage2.getDescription() + " NT$" + beverage2.cost());
        // 預期價格: 60 (咖啡) + 15 (牛奶) + 20 (摩卡) + 20 (摩卡) = 115
    }
}

執行結果:

飲料1: 濃縮咖啡 NT$60.0
---------------------------------
飲料2: 濃縮咖啡, 加牛奶, 加摩卡, 加摩卡 NT$115.0

正如你所見,我們不需要建立 EspressoWithMilkAndDoubleMocha 這種子類別,而是透過一層層的包裝,動態地組合出了我們想要的功能和價格。


上一篇
Day14-Code design我遇到比較常見的patten/單例模式 (Singleton Pattern)
系列文
一條龍的軟體開發到維護,從校園工讀到職場工程師15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言