---
# System prepended metadata

title: 10. Decorator
tags: [DesignPatterns]

---

# 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
![](https://i.imgur.com/qNVTmyP.jpg)

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/)

---

## 新手上路，若有錯誤還請告知，謝謝