目的/使用時機

  • 動態的對物件增加額外的職責,且不需修改既有的程式。

  • 使用合成的方式動態增加職責,而非使用繼承(繼承是靜態的,且繼承相比合成有更高的耦合性)。

  • 把一個禮物先裝在盒子裡,盒子外面再包一層包裝紙,再綁上絲帶 = 透過一層一層包裝(ConcreteDecorator)的方式把禮物(ConcreteComponent)包裝起來。

  • 也算是一種 wrapper 的作法。


Participants

  • Component: 用來定義動態附加的介面,給 wrappers 跟 被 wrap 的物件使用。

  • ConcreteComponent: 要被 warp 的物件(可以被附加責任的物件),可以先給予初始化的內容。

  • Decorator: 裡面會有一個被 wrap 的物件。(一般是使用建構子將帶入 ConcreteComponent)

  • ConcreteDecorator: 向 Component 添加職責的物件,再調用自己的 Method 前先調用父類別的。


UML


優點

  • 使用合成取代繼承,提供更多彈性。

  • 具體類別(ConcreteComponent)與裝飾類別獨立設計,可依據需求動態增加裝飾的職責,且不需修改原有具體類別,符合開放封閉原則。

  • 把類別的核心職責與裝飾功能區分開來,符合單一職責原則。


缺點

  • 若使用過多的裝飾類別會增加理解難度。

  • 使用合成的方式增加職責(多次裝飾對象),除錯時需逐步檢查,增加除錯難度。

  • 若裝飾的順序會影響功能,則不建議使用,理想情況是裝飾類別彼此之間獨立,這樣就可以任意排列組合使用。

  • 若要動態移除特定的裝飾功能較困難。


範例

寫一漢堡類別,允許可動態地增加其多種配料,並計算出最後價錢。

UML:

定義一漢堡虛擬類別 (Component):

/// <summary>
/// Component
/// </summary>
public abstract class Hamburger
{
    public abstract string Name { get; }

    public abstract int Price { get; }
}

定義兩個基本款漢堡:起司堡、大麥克 (ConcreteComponent):

/// <summary>
/// ConcreteComponent
/// </summary>
public class Cheeseburger : Hamburger
{
    public override string Name
    {
        get => "Cheeseburger";
    }

    public override int Price
    {
        get => 80;
    }
}

/// <summary>
/// ConcreteComponent
/// </summary>
public class BigMac : Hamburger
{
    public override string Name
    {
        get => "BigMac";
    }

    public override int Price
    {
        get => 100;
    }
}

裝飾類別,繼承漢堡類別 (Decorator):

/// <summary>
/// Decorator
/// </summary>
public abstract class BurgerDecoratorBase : Hamburger
{
    protected Hamburger _hamburger;

    public BurgerDecoratorBase(Hamburger hamburger)
    {
        _hamburger = hamburger;
    }
}

實際要加入漢堡的東西,繼承裝飾類別 (ConcreteDecorator):

/// <summary>
/// ConcreteDecorator
/// </summary>
public class BeefDecorator : BurgerDecoratorBase
{
    public BeefDecorator(Hamburger hamburger) : base(hamburger)
    {
    }

    public override string Name
    {
        get => _hamburger.Name + ", add Beef";
    }

    public override int Price
    {
        get => _hamburger.Price + 20;
    }
}

/// <summary>
/// ConcreteDecorator
/// </summary>
public class LettuceDecorator : BurgerDecoratorBase
{
    public LettuceDecorator(Hamburger hamburger) : base(hamburger)
    {
    }

    public override string Name
    {
        get => _hamburger.Name + ", add Lettuce";
    }

    public override int Price
    {
        get => _hamburger.Price + 5;
    }
}

/// <summary>
/// ConcreteDecorator
/// </summary>
public class PicklesDecorator : BurgerDecoratorBase
{
    public PicklesDecorator(Hamburger hamburger) : base(hamburger)
    {
    }

    public override string Name
    {
        get => _hamburger.Name + ", add Pickles";
    }

    public override int Price
    {
        get => _hamburger.Price + 5;
    }
}

client 端:

Hamburger burger1 = new Cheeseburger();     // +80
burger1 = new BeefDecorator(burger1);       // +20
burger1 = new LettuceDecorator(burger1);    // +5
burger1 = new PicklesDecorator(burger1);    // +5
burger1 = new BeefDecorator(burger1);       // +20

Console.WriteLine("burger1 Name: " + burger1.Name);     // burger1 Name: Cheeseburger, add Beef, add Lettuce, add Pickles, add Beef
Console.WriteLine("burger1 Price: " + burger1.Price);   // burger1 Price: 130
Console.WriteLine("======================================");

Hamburger burger2 = new BigMac();           // +100
burger2 = new LettuceDecorator(burger2);    // +5
burger2 = new PicklesDecorator(burger2);    // +5

Console.WriteLine("burger2 Name: " + burger2.Name);     // burger2 Name: BigMac, add Lettuce, add Pickles
Console.WriteLine("burger2 Price: " + burger2.Price);   // burger2 Price: 110
Console.WriteLine("======================================");

輸出結果:

burger1 Name: Cheeseburger, add Beef, add Lettuce, add Pickles, add Beef
burger1 Price: 130
======================================
burger2 Name: BigMac, add Lettuce, add Pickles
burger2 Price: 110
======================================