引言
當我們完成一個軟件產品開發后就需要對其進行各種測試,適配快速迭代下質量的保障。當有一個完善的產品的對象后,如果我們想要給他添加一個測試功能,那么我們可以用一個新的類去裝飾它來實現對原有對象職責的擴展。新的類稱為“裝飾者”,原有的對象稱為“被裝飾者”。這種模式被稱為裝飾器模式。
概念
裝飾器模式(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
應用場景
- 在無需修改代碼的情況下即可使用對象, 且希望在運行時為對象新增額外的行為時可以使用裝飾模式。因為裝飾能將業務邏輯組織為層次結構,可為各層創建一個裝飾, 在運行時將各種不同邏輯組合成對象。 由於這些對象都遵循通用接口, 客戶端代碼能以相同的方式使用這些對象。
- 如果用繼承來擴展對象行為的方案難以實現或者根本不可行,可以使用裝飾模式。
優缺點
優點
- 無需創建新子類即可擴展對象的行為。
- 可以在運行時添加或刪除對象的功能。
- 可以用多個裝飾封裝對象來組合幾種行為。
- 裝飾類和被裝飾類可以獨立發展,不會相互耦合。
- 單一職責原則。 可以將實現了許多不同行為的一個大類拆分為多個較小的類。
缺點
- 在封裝器棧中刪除特定封裝器比較困難。
- 實現行為不受裝飾棧順序影響的裝飾比較困難。
- 各層的初始化配置代碼看上去可能會很糟糕。