C++ 設計模式 - 裝飾者模式 Decorator Pattern

在日常生活中,我們經常會去咖啡店買咖啡,但如果想要來點變化,像是加一點牛奶或糖,就可以讓平凡的咖啡多一點風味。同樣地,在程式設計中,我們有時也需要在不改變原有功能的前提下,為某些物件「加點料」。這就是裝飾者模式 Decorator Pattern 所要解決的問題。

什麼是裝飾者模式?

裝飾者模式是一種結構型設計模式,它允許你動態地為物件添加功能,而不需要修改其原有的程式碼。換句話說,裝飾者模式讓你可以根據不同的需求,隨時為一個物件「增添佐料」,就像你為咖啡加牛奶或糖一樣,都會改變咖啡的口感和價格,但我們並沒有改變咖啡本身。

裝飾者模式在咖啡店的應用

讓我們用一個簡單的例子來說明。假設我們有一個基本的咖啡類別 Coffee,它代表一杯不加任何配料的黑咖啡。你可以根據需求選擇加入牛奶、糖或其他配料。使用裝飾者模式,我們可以輕鬆地計算出各種組合的價格,而不需要為每種可能的組合建立新的類別。

首先,我們定義一個 Coffee 類別,並為它計算基本價錢的功能:

1
2
3
4
5
6
7
8
9
10
class Coffee {
public:
virtual std::string getDescription() const {
return "黑咖啡";
}
virtual int cost() const {
return 50;
}
virtual ~Coffee() = default;
};

假設你想在咖啡中加牛奶,我們可以建立一個裝飾者類別 MilkDecorator,這個類別將會裝飾(也就是包裝)Coffee 類別,並在其基礎上增加牛奶的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MilkDecorator : public Coffee {
private:
std::unique_ptr<Coffee> coffee;

public:
MilkDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}

std::string getDescription() const override {
return coffee->getDescription() + "+牛奶";
}

int cost() const override {
return coffee->cost() + 15;
}
};

如果你想再加點糖,可以再建立一個 SugarDecorator 類別:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SugarDecorator : public Coffee {
private:
std::unique_ptr<Coffee> coffee;

public:
SugarDecorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) {}

std::string getDescription() const override {
return coffee->getDescription() + "+糖";
}

int cost() const override {
return coffee->cost() + 5;
}
};

最後我們就可以根據自己的需求自由組合這些裝飾者:

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {
std::unique_ptr<Coffee> coffee = std::make_unique<Coffee>();
std::cout << coffee->getDescription() << "價格:"
<< coffee->cost() << "元\n";
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
std::cout << coffee->getDescription() << "價格:"
<< coffee->cost() << "元\n";
coffee = std::make_unique<SugarDecorator>(std::move(coffee));
std::cout << coffee->getDescription() << "價格:"
<< coffee->cost() << "元\n";

return 0;
}

執行上述程式碼,我們會得到以下輸出:

1
2
3
黑咖啡價格:50元
黑咖啡+牛奶價格:65元
黑咖啡+牛奶+糖價格:70元

在這個範例中,我們從一杯基本的黑咖啡開始,然後動態地添加牛奶和糖,最終計算出一杯加了牛奶和糖的咖啡價格。每次添加新配料時,我們不需要修改現有的咖啡類別,只需透過裝飾者來實現功能的擴展。這樣的設計讓我們可以靈活地擴展功能,也讓程式碼更加易於維護。

裝飾者模式的優點

裝飾者模式的最大優點就是靈活性,你可以任意組合裝飾者成各種不同的組合。當你面對需要不斷添加新功能的情況時,使用裝飾者模式可以避免類別數量的膨脹,讓程式碼更易於維護和擴展。同時它也保持了開放封閉原則,也就是添加新的裝飾者不需要修改原始類別,也符合單一職責原則,即每個裝飾者只負責一項特定的功能增強。

總結

裝飾者模式就像是在咖啡裡添加牛奶和糖,讓原本簡單的東西變得更加豐富多樣。在程式設計中,它能幫助我們以一種靈活的方式,擴展物件的功能,同時保持程式碼的清晰與可維護性。下次當你面臨需要擴展物件功能的需求時,考慮一下裝飾者模式,它或許就是你需要的解決方案!