[Design Pattern]裝飾模式(Decorator Pattern)
我們都知道真實世界中需求異動是無法避免的,
好比一開始報表功能可能只有顯示和下載2大基本功能,
但後來使用者希望顯式報表時,要先檢查是否有權限,
又過沒多久使用者也希望下載前也要檢查權限,
在沒多久,使用者又希望權限檢查後,還要判斷系統日期是否為月底...等。
設計上你可能會利用物件導向繼承特性,
新增相關特定子類別(單一職責原則,提高內聚)繼承父類別,
然後特定子類別中撰寫特有方法,
但這樣的設計導致Client端需要知道該使用什麼樣的子類別(應該只能透過介面),
同時也違反LSP替換原則(使用父類別的地方都可以使用子類別取代,不需要任何改變),
那或許你又會說,那乾脆在父類別新增相關功能方法不就得了,
但如果使用者又打算取消某功能,
這樣你就得修改父類別(增加系統錯誤風險),
並進行重新編譯、測試...等一連串動作(這些動作看起來還真有些不合理),
所以為了要解決後續新增功能問題,
最好的方法就是不要修改已經通過測試的程式碼(符合開發封閉原則:對修改封閉,對擴充開放),
藉以降低系統可能的錯誤風險,這時設計就可以考慮使用裝飾模式來達到動態擴充相關功能(不採取繼承改採取組合)。
UML
需求:
檢查報表顯示和下載權限( rico 才有權限),且報表顯示後要確認系統日是否小於30號,
如果小於請警告使用者。
下面是利用裝飾模式的程式碼
報表執行如下
Client端呼叫
static void Main(string[] args)
{
IReportControl myreport = new SimpleReportControl();
ReportDecorator checkauth = new Authority(myreport) { Name="rico"};
ReportDecorator checksysdate = new SystemDate(myreport);
checkauth.Show();
checksysdate.Show();
checkauth.DownLoad();
Console.ReadLine();
}
/// <summary>
/// 報表介面
/// </summary>
public interface IReportControl
{
void Show();
void DownLoad();
}
/// <summary>
/// 被裝飾者
/// </summary>
public class SimpleReportControl : IReportControl
{
public void Show()
{
Console.WriteLine("顯示報表");
}
public void DownLoad()
{
Console.WriteLine("下載報表");
}
}
/// <summary>
/// 裝飾者
/// </summary>
public abstract class ReportDecorator : IReportControl
{
private IReportControl _IReportControl;
public ReportDecorator(IReportControl ireportControl)
{
this._IReportControl = ireportControl;
}
#region 虛擬方法
public virtual void Show()
{
this._IReportControl.Show();
}
public virtual void DownLoad()
{
this._IReportControl.DownLoad();
}
#endregion
}
/// <summary>
/// 擴充權限檢查(負責幫裝飾者新增方法)
/// </summary>
public class Authority : ReportDecorator
{
public string Name { get; set; }
public Authority(IReportControl ireportControl)
:base(ireportControl)
{
}
public override void Show()
{
if (CheckAut())
{
Console.WriteLine("權限檢查正常");
base.Show();
}
else
Console.WriteLine("無相關權限");
}
public override void DownLoad()
{
if (CheckAut())
{
Console.WriteLine("權限檢查正常");
base.DownLoad();
}
else
Console.WriteLine("無相關權限");
}
#region 權限檢查
public bool CheckAut()
{
if (this.Name == "rico")
return true;
else
return false;
}
#endregion
}
/// <summary>
/// 擴充系統日期檢查(負責幫裝飾者新增方法)
/// </summary>
public class SystemDate : ReportDecorator
{
public SystemDate(IReportControl ireportControl)
:base(ireportControl)
{
}
public override void Show()
{
if (CheckSysDate())
{
Console.WriteLine("系統日期檢查正常");
base.Show();
}
else
Console.WriteLine("系統日期小於30");
}
public override void DownLoad()
{
base.DownLoad();
}
#region 系統日期檢查
public bool CheckSysDate()
{
if (DateTime.Today.Day >= 30)
return true;
else
return false;
}
#endregion
}
參考