C#設計模式-裝飾器模式(Decorator Pattern)

引言

當我們完成一個軟體產品開發後就需要對其進行各種測試,適配快速迭代下品質的保障。當有一個完善的產品的對象後,如果我們想要給他添加一個測試功能,那麼我們可以用一個新的類去裝飾它來實現對原有對象職責的擴展。新的類稱為「裝飾者」,原有的對象稱為「被裝飾者」。這種模式被稱為裝飾器模式。

概念

裝飾器模式(Decorator Pattern)允許向一個現有的對象添加新的功能,同時又不改變其結構。這種類型的設計模式屬於結構型模式,它是作為現有的類的一個包裝。
這種模式創建了一個裝飾類,用來包裝原有的類,並在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的實例來演示裝飾器模式的用法。其中,我們將把一個形狀裝飾上不同的顏色,同時又不改變形狀類。

結構圖

裝飾器模式中的角色:

  • 抽象構件(Component)角色:聲明封裝器和被封裝對象的公用介面。即給出一個抽象介面,已規範準備接收附加責任的對象。
  • 具體構件(ConcreteComponent)角色:類是被封裝對象所屬的類。 它定義了基礎行為, 但裝飾類可以改變這些行為。
  • 裝飾(Decorator)角色:擁有一個指向被封裝對象的引用成員變數。 該變數的類型應當被聲明為通用部件介面, 這樣它就可以引用具體的部件和裝飾。 裝飾基類會將所有操作委派給被封裝的對象。
  • 具體裝飾(ConcreteDecorator)角色:定義了可動態添加到部件的額外行為。 具體裝飾類會重寫裝飾基類的方法, 並在調用父類方法之前或之後進行額外的行為。負責給構件對象「貼上」附加的責任。

實現

實現一個開發完成後的產品,對其進行手工功能測試、自動化測試、壓力測試。將產品作為被裝飾者,也就是構件。各種測試作為裝飾者,計算附加不同的測試花費的總時間。

實現思路:

  • 定義一個產品抽象類。
  • 實現具體的產品,具體的產品繼承產品抽象類。
  • 定義一個測試類型的抽象裝飾類,繼承產品抽象類。
  • 實現不同類型的測試,繼承測試類型的抽象裝飾類。
  • 使用時實例化一個產品,然後對產品進行附件不同的測試類型。
using System;


namespace Decorator
{
    class Program
    {
        static void Main(string[] args)
        {
            Product a = new ProductA();
            Console.WriteLine($"未執行{a.Test}測試時總共花費時間{a.TotalTime}");
            a = new ManualTest(a);
            Console.WriteLine($"執行{a.Test}總共花費時間{a.TotalTime}");
            Console.WriteLine("=====================================");

            Product b = new ProductB();
            b = new ManualTest(b);
            b = new StressTest(b);
            b = new AutoTest(b);
            Console.WriteLine($"執行{b.Test}總共花費時間{b.TotalTime}");

            Console.Read();
        }
    }

    /// <summary>
    /// 一個項目產品,抽象構件
    /// </summary>
    public abstract class Product
    {
        public string Name { get; set; }
        public double SpendTime { get; set; }
        public abstract string Test { get; }
        public abstract double TotalTime { get; }
    }

    /// <summary>
    /// 具體的項目產品A,具體構件
    /// </summary>
    public class ProductA : Product
    {
        public ProductA()
        {
            Name = "ProductA";
            SpendTime = 0;
        }

        public override string Test => this.Name;
        public override double TotalTime => this.SpendTime;
    }

    /// <summary>
    /// 具體的項目產品B,具體構件
    /// </summary>
    public class ProductB : Product
    {
        public ProductB()
        {
            Name = "ProductB";
            SpendTime = 0;
        }

        public override string Test => this.Name;
        public override double TotalTime => this.SpendTime;
    }

    /// <summary>
    /// 測試類型,抽象裝飾
    /// </summary>
    public abstract class TestType : Product
    {
        Product _product = null;

        public TestType(Product product)
        {
            _product = product;
        }

        public override string Test
        {
            get
            {
                return _product.Test + "+" + this.Name;
            }
        }

        public override double TotalTime
        {
            get
            {
                return _product.TotalTime + this.SpendTime;
            }
        }
    }

    /// <summary>
    /// 手工測試類型,具體裝飾
    /// </summary>
    public class ManualTest : TestType
    {

        public ManualTest(Product product) : base(product)
        {
            Name = "手工測試";
            SpendTime = 200;
        }
    }

    /// <summary>
    /// 自動化測試類型,具體裝飾
    /// </summary>
    public class AutoTest : TestType
    {

        public AutoTest(Product product) : base(product)
        {
            Name = "自動化測試";
            SpendTime = 100;
        }

    }

    /// <summary>
    /// 壓力測試類型,具體裝飾
    /// </summary>
    public class StressTest : TestType
    {
        public StressTest(Product product) : base(product)
        {
            Name = "壓力測試";
            SpendTime = 200;
        }
    }

}

運行後結果:

未執行ProductA測試時總共花費時間0
執行ProductA+手工測試總共花費時間200
=====================================
執行ProductB+手工測試+壓力測試+自動化測試總共花費時間500

應用場景

  • 在無需修改程式碼的情況下即可使用對象, 且希望在運行時為對象新增額外的行為時可以使用裝飾模式。因為裝飾能將業務邏輯組織為層次結構,可為各層創建一個裝飾, 在運行時將各種不同邏輯組合成對象。 由於這些對象都遵循通用介面, 客戶端程式碼能以相同的方式使用這些對象。
  • 如果用繼承來擴展對象行為的方案難以實現或者根本不可行,可以使用裝飾模式。

優缺點

優點

  • 無需創建新子類即可擴展對象的行為。
  • 可以在運行時添加或刪除對象的功能。
  • 可以用多個裝飾封裝對象來組合幾種行為。
  • 裝飾類和被裝飾類可以獨立發展,不會相互耦合。
  • 單一職責原則。 可以將實現了許多不同行為的一個大類拆分為多個較小的類。

缺點

  • 在封裝器棧中刪除特定封裝器比較困難。
  • 實現行為不受裝飾棧順序影響的裝飾比較困難。
  • 各層的初始化配置程式碼看上去可能會很糟糕。