關於 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
- 動態的將職責附加至物件
- 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 轉為程式碼
被裝飾物件的抽象類別
/// <summary>
/// 被裝飾物件的抽象類別
/// </summary>
public abstract class Component
{
public abstract void Operation();
}
實作被裝飾物件
/// <summary>
/// 實作被裝飾物件
/// </summary>
public class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("ConcreteComponent.Operation()");
}
}
裝飾者抽象類別,繼承被裝飾者抽象類別
/// <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
/// <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...
}
}
- 建立被裝飾者
component
- 建立裝飾者
decorator
- 被裝飾者
component
套用裝飾者 AdecoratorA
- 再套用裝飾者 B
decoratorB
- 呼叫
Operation()
執行
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();
}
執行結果
ConcreteComponent.Operation()
ConcreteDecoratorA.Operation()
ConcreteDecoratorB.Operation()
4. 情境
我們接到了一個要能配合行銷策略安排各種折扣(e.g. 周年慶全面九折、滿千折百無上限)的需求
- 已知折扣 1. 周年慶全面九折 2. 滿千折百無上限,且未來可能會再擴充
- 折扣要可以疊加
計算售價抽象類別
/// <summary>
/// 計算售價抽象類別
/// </summary>
public abstract class Calculation
{
public abstract int Price();
}
計算售價實作
/// <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;
}
}
裝飾者抽象類別,計算售價抽象類別
/// <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;
}
}
實作裝飾者,全面九折 & 滿千折百無上限
/// <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;
}
}
計算售價實作套用折扣裝飾者
- 建立(被裝飾者)計算金額
calculation
- 建立(裝飾者)全面九折
allTenPercentOff
& 滿千送百every1000Get100CashBack
- 計算金額
calculation
套用全面九折allTenPercentOff
- 再套用滿千送百
every1000Get100CashBack
- 呼叫
Price()
取得金額
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();
}
執行結果
結帳金額(原價):5000
結帳金額(全面九折):4500
結帳金額(滿千折百無上限):4100
完整程式碼
GitHub:Structural_04_Decorator