# 10. Decorator ###### tags: `DesignPatterns` ## 關於 Decorator 本篇將討論以下幾個問題 > ### 1. 關於 Decorator > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Decorator > Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 動態的將職責附加至物件 - Decorator 為子類別提供靈活的擴展 Decorator(裝飾者)屬於結構型(Structural Patterns),當遇到需要**對物件附加一個以上的處理邏輯**,可使用 Decorator 依據需求將原始物件依序處理後達到目的,且不需要另外處理時也可直接呼叫原始物件來使用。如同俄羅斯娃娃,有著相同的外觀(接口),只有大小不同(處理),依據需求取得所需大小即可。 優點: - 符合 開閉原則(Open Closed Principle) 缺點: - 違反 介面隔離原則(Interface Segregation Principle) --- ## 2. UML  Class 間關聯: - ConcreteComponent & Decorator 繼承 Component - ConcreteDecorator A / B 繼承 Decorator - Decorator 可包含 Component Class: - Component:被裝飾物件的抽象類別 - ConcreteComponent:實作被裝飾物件 - Decorator:裝飾者抽象類別,繼承被裝飾者抽象類別 - ConcreteDecorator:實作裝飾者,可附加至被裝飾物件的處理邏輯 --- ## 3. 將 UML 轉為程式碼 被裝飾物件的抽象類別 ```C# /// <summary> /// 被裝飾物件的抽象類別 /// </summary> public abstract class Component { public abstract void Operation(); } ``` 實作被裝飾物件 ```C# /// <summary> /// 實作被裝飾物件 /// </summary> public class ConcreteComponent : Component { public override void Operation() { Console.WriteLine("ConcreteComponent.Operation()"); } } ``` 裝飾者抽象類別,繼承被裝飾者抽象類別 ```C# /// <summary> /// 裝飾者抽象類別,繼承被裝飾者抽象類別 /// </summary> public abstract class Decorator : Component { protected Component component; public void SetComponent(Component component) { this.component = component; } public override void Operation() { if (component != null) { component.Operation(); } } } ``` 實作裝飾者 A & B ```C# /// <summary> /// 實作裝飾者 A /// </summary> public class ConcreteDecoratorA : Decorator { public override void Operation() { base.Operation(); Console.WriteLine("ConcreteDecoratorA.Operation()"); } } /// <summary> /// 實作裝飾者 B /// </summary> public class ConcreteDecoratorB : Decorator { public override void Operation() { base.Operation(); AddedBehavior(); Console.WriteLine("ConcreteDecoratorB.Operation()"); } void AddedBehavior() { // Do something... } } ``` 1. 建立被裝飾者`component` 2. 建立裝飾者`decorator` 3. 被裝飾者`component`套用裝飾者 A `decoratorA` 4. 再套用裝飾者 B `decoratorB` 5. 呼叫`Operation()`執行 ```C# static void Main(string[] args) { Default.ConcreteComponent component = new Default.ConcreteComponent(); Default.ConcreteDecoratorA decoratorA = new Default.ConcreteDecoratorA(); Default.ConcreteDecoratorB decoratorB = new Default.ConcreteDecoratorB(); decoratorA.SetComponent(component); decoratorB.SetComponent(decoratorA); decoratorB.Operation(); Console.ReadLine(); } ``` 執行結果 ```Console ConcreteComponent.Operation() ConcreteDecoratorA.Operation() ConcreteDecoratorB.Operation() ``` --- ## 4. 情境 我們接到了一個要能配合行銷策略安排各種折扣(e.g. 周年慶全面九折、滿千折百無上限)的需求 - 已知折扣 1. 周年慶全面九折 2. 滿千折百無上限,且未來可能會再擴充 - 折扣要可以疊加 計算售價抽象類別 ```C# /// <summary> /// 計算售價抽象類別 /// </summary> public abstract class Calculation { public abstract int Price(); } ``` 計算售價實作 ```C# /// <summary> /// 計算售價實作 /// </summary> public class ConcreteCalculation : Calculation { private int _Price { get; } public ConcreteCalculation(int price) { _Price = price; } public override int Price() { Console.WriteLine($"結帳金額(原價):{_Price}"); return _Price; } } ``` 裝飾者抽象類別,計算售價抽象類別 ```C# /// <summary> /// 裝飾者抽象類別,繼承被裝飾者抽象類別 /// </summary> public abstract class Decorator : Calculation { protected Calculation Calculation; public void SetComponent(Calculation calculation) { this.Calculation = calculation; } public override int Price() { return Calculation?.Price()?? 0; } } ``` 實作裝飾者,全面九折 & 滿千折百無上限 ```C# /// <summary> /// 全面九折 /// </summary> public class AllTenPercentOff : Decorator { public override int Price() { var getPrice = base.Price(); var newPrice = (int) (getPrice * 0.9); Console.WriteLine($"結帳金額(全面九折):{newPrice}"); return newPrice; } } /// <summary> /// 滿千折百無上限 /// </summary> public class Every1000Get100CashBack : Decorator { public override int Price() { var getPrice = base.Price(); var discountTimes = (int)Math.Floor((decimal)(getPrice / 1000)); var newPrice = getPrice - (100 * discountTimes); Console.WriteLine($"結帳金額(滿千折百無上限):{newPrice}"); return newPrice; } } ``` 計算售價實作套用折扣裝飾者 1. 建立(被裝飾者)計算金額`calculation` 2. 建立(裝飾者)全面九折`allTenPercentOff` & 滿千送百`every1000Get100CashBack` 3. 計算金額`calculation`套用全面九折`allTenPercentOff` 4. 再套用滿千送百`every1000Get100CashBack` 5. 呼叫`Price()`取得金額 ```C# static void Main(string[] args) { Situation.ConcreteCalculation calculation = new Situation.ConcreteCalculation(5000); Situation.AllTenPercentOff allTenPercentOff = new Situation.AllTenPercentOff(); Situation.Every1000Get100CashBack every1000Get100CashBack = new Situation.Every1000Get100CashBack(); allTenPercentOff.SetComponent(calculation); every1000Get100CashBack.SetComponent(allTenPercentOff); every1000Get100CashBack.Price(); Console.ReadLine(); } ``` 執行結果 ```Console 結帳金額(原價):5000 結帳金額(全面九折):4500 結帳金額(滿千折百無上限):4100 ``` --- ## 完整程式碼 GitHub:[Structural_04_Decorator](https://github.com/darionnnnnn/blog/tree/master/Blog/Structural_04_Decorator) --- ## 總結 ### 本篇沒有將 abstract class 改為 interface,主要是因為覺得 Decorator 使用繼承的方式能使子類別更加簡潔。雖然常聽到「用組合取代繼承」的建議,但繼承的使用若只是一兩階層而已,則不需要太過計較非使用組合或是繼承不可,依據使用情況選擇適合的即可。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up