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

Tynam.Yang發表於2020-11-26

引言

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

概念

裝飾器模式(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

應用場景

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

優缺點

優點

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

缺點

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

 

相關文章