iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
自我挑戰組

Design Pattern - 無所不在的設計模式系列 第 15

[Day15] Design Pattern - Decorator裝飾者模式

  • 分享至 

  • xImage
  •  

定義


Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
--Refactoring Guru

Decorator(裝飾者模式)是一種結構型設計模式,它允許你通過將物件放置在包含特定行為的特殊包裝物件中,來附加新的行為到這些物件上。

為什麼要有裝飾者模式?


裝飾者模式通常是為了解決以下問題而產生的:

  1. 靈活擴展功能
    當你希望能夠動態地向物件添加新功能或行為,而不需要修改其原始類別程式碼時,裝飾者模式是一種有用的解決方案。
    它允許你將功能封裝在獨立的裝飾者類別中,然後組合這些裝飾者以實現所需的功能組合,從而使系統更具靈活性。

  2. 開放封閉原則
    裝飾者模式有助於遵守軟體工程中的開放-封閉原則。這個原則指出軟體實體(如類別、模組等)應該對擴展(extension)是開放的,對修改(modification)是封閉的。通過使用裝飾者模式,你可以在不修改現有程式碼的情況下擴展功能,從而遵守這一原則。

  3. 避免程式碼膨脹
    當你需要支援多種功能組合時,傳統的做法是創建大量的子類別,每個子類別對應一種功能組合。這會導致類別層次的膨脹,並使程式碼難以維護。Decorator模式通過將功能拆分成獨立的裝飾者,使得你只需創建少量的裝飾者類別,以實現多種功能組合,而不需要大量的子類別。
    例如下圖在建立Notifier時,漸漸膨脹的各個子類別...
    Refactoring Guru

總之,裝飾者模式的主要目標是允許你以靈活、可擴展和遵守開放-封閉原則的方式擴展物件的功能,同時保持程式碼的清晰和可維護性。它解決了在不修改現有程式碼的情況下添加功能和避免程式碼膨脹等問題。

UML


舉個例子吧~


現實生活中最簡單的例子就是人穿衣服,例如下雨穿雨衣、去海邊穿泳衣、天氣冷穿外套;如果還是很冷就再穿一件毛衣!
這些原本不在人身上的衣物,那些衣服就像decorators一樣,但它不屬於人的一部份,當你不穿的時候可以很簡單的和下來

C#實作例子

這次來開一家蛋糕店~
蛋糕店推出一款基本款蛋糕,
可以組合其他三種配料

首先是蛋糕介面:

// 蛋糕介面
public interface ICake
{
    string GetDescription();
    double GetCost();
}

基本款蛋糕具體類別:

// 基本款蛋糕類別
public class BasicCake : ICake
{
    public string GetDescription()
    {
        return "基本款蛋糕";
    }

    public double GetCost()
    {
        return 20.0;
    }
}

抽象裝飾者類別:

// 裝飾者抽象類別
public abstract class CakeDecorator : ICake
{
    protected ICake cake;

    public CakeDecorator(ICake cake)
    {
        this.cake = cake;
    }

    public virtual string GetDescription()
    {
        return cake.GetDescription();
    }

    public virtual double GetCost()
    {
        return cake.GetCost();
    }
}

再來是我們的裝飾者物件們:

// 巧克力裝飾者
public class ChocolateDecorator : CakeDecorator
{
    public ChocolateDecorator(ICake cake) : base(cake)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + " + 巧克力";
    }

    public override double GetCost()
    {
        return base.GetCost() + 5.0;
    }
}

// 奶油裝飾者
public class CreamDecorator : CakeDecorator
{
    public CreamDecorator(ICake cake) : base(cake)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + " + 奶油";
    }

    public override double GetCost()
    {
        return base.GetCost() + 6.0;
    }
}

// 水果裝飾者
public class FruitDecorator : CakeDecorator
{
    public FruitDecorator(ICake cake) : base(cake)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + " + 水果";
    }

    public override double GetCost()
    {
        return base.GetCost() + 8.0;
    }
}

// 果醬裝飾者
public class JamDecorator : CakeDecorator
{
    public JamDecorator(ICake cake) : base(cake)
    {
    }

    public override string GetDescription()
    {
        return base.GetDescription() + " + 果醬";
    }

    public override double GetCost()
    {
        return base.GetCost() + 4.0;
    }
}

最後是我們的客戶端程式碼:

class Program
{
    static void Main(string[] args)
    {
        // 創建基本款蛋糕
        ICake basicCake = new BasicCake();
        Console.WriteLine($"蛋糕描述: {basicCake.GetDescription()}");
        Console.WriteLine($"價格: {basicCake.GetCost()} 元");

        // 創建帶巧克力和奶油的蛋糕
        ICake chocolateCreamCake = new CreamDecorator(new ChocolateDecorator(basicCake));
        Console.WriteLine($"蛋糕描述: {chocolateCreamCake.GetDescription()}");
        Console.WriteLine($"價格: {chocolateCreamCake.GetCost()} 元");

        // 創建帶水果和果醬的蛋糕
        ICake fruitJamCake = new JamDecorator(new FruitDecorator(basicCake));
        Console.WriteLine($"蛋糕描述: {fruitJamCake.GetDescription()}");
        Console.WriteLine($"價格: {fruitJamCake.GetCost()} 元");
        
        //在這裡新增其他組合~~~
    }
}

來看看output吧:


上一篇
[Day14] Design Pattern - Composite組合模式
下一篇
[Day16] Design Pattern - Facade 外觀模式
系列文
Design Pattern - 無所不在的設計模式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言