Decorator Pattern (裝飾器模式)
裝飾器模式是一種結構型設計模式,可以動態地為物件附加額外的職責。
裝飾器模式結構

裝飾器模式的應用場景
- 如果你希望在無需修改程式碼的情況下即可使用對象,並且希望在運行時為對象增加額外的"行為"
裝飾器模式能將業務邏輯組織成層次結構,可以為各層創建一個裝飾,在運行期間將各種不同的邏輯組合成對象。這些對象都遵循通用介面,客戶端地程式碼能以相同方式使用對象。
- 如果用繼承來擴展對象行為的方案難以實現
許多程式使用 final 限制類的進一步擴展,復用最終類已有的行為的唯一方法就是裝飾器模式:用封裝器對其進行封裝。
優缺點
⭕優點
- 無須創建新子類即可擴展對象的行為
- 可以在運行期間添加或刪除對象的功能
- 可以用多個裝飾封裝對象來組合多種行為
- 單一職責原則。可以將實現了許多不同行為的一個大類,拆分成許多較小的類
❌缺點
- 在裝飾器 stack 中刪除特定的裝飾器比較困難
- 實現行為不受裝飾 stack 順序影響的裝飾比較困難
- 各層的初始化配置程式碼看上去可能會很糟糕
- 會產生很多小對象,同時也會產生很多具裝飾類,會增加系統的複雜度和學習與理解難度,有時需要工廠模式協助
原始的模型架構
- 創立初期,使用抽象介面定義飲料(Beverage),咖啡店的所有飲料都繼承他。
- description 用來保存飲料的敘述。
- cost()由子類別實作,用來回傳飲料的價格。

遇到的需求與問題
- 因為飲料增加了調味料,如牛奶、豆漿、摩卡、奶泡。根據不同的調味品加收不同的費用,造成類別大爆炸。

錯誤方法
- 將調味料放入飲料類別,透過 has 去判斷是否有調味料,在每一個子類的 cost()取得調味品,並計算加上調味品的價格

違反開放/封閉原則。未來調味料越多,Beverage 和子類的 cost 就必須跟著修改。
開放/封閉原則
我們的目標是讓類別容易擴展,藉以納入新行為,但是不能修改既有的程式碼。實現這個目標有什麼好處?這種設計不但有因應改變的韌性,也有足夠的彈性,可以接納新功能,來滿足不斷改變的需求。
類別應該歡迎擴展,但拒絕修改
Classes should be open for extension, but closed for modification.
類別應該對擴展開放,對修改封閉
沒有蠢問題
Q: 該怎麼讓設計的每一個部分都遵守開放/封閉原則?
A: 這通常不可能做到。一般來說,我們沒有那麼多資源可以把設計的每一個細節都做成這樣。遵守開放/封閉原則通常會引入新一層的抽象,讓程式更複雜。應該把注意力放在最有可能改變的地方,並且在那裡實施這些規則。
請謹慎地選擇需要擴展的部分,到處採用開放/封閉原則不但浪費,也沒有必要,甚至可能寫出複雜的、難以理解的程式。
裝飾器概念


Q: CondimentDecorator 繼承 Beverage 類別,這不是繼承關係嗎?
A: 為了讓裝飾器和被他裝飾的物件有相同的型態,使用繼承讓他們有相同的型態,這裡的繼承不是為了獲得行為。
Q: 行為是怎麼加入的?
A: 我們不是藉著繼承超類別來獲得新行為,而是藉著將物件組合起來。因為依靠繼承,行為只能在編譯其決定,固定不變。但組合可以在執行期間,動態隨意混合搭配裝飾器。
Q: 為什麼不把 Beverage 設計成介面?
A: 原本就有一個抽象的 Beverage,當然可以使用介面,但通常為了避免修改既有的程式碼,所以不會在抽象類別沒有任何問題時修正他 。
程式碼
- 不需要修改原本的抽象飲料類別
public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() { // 寫好通用的方法
return description;
}
public abstract double cost(); // 在子類別裡面實作
}