[Design Pattern]裝飾模式(Decorator Pattern)

[Design Pattern]裝飾模式(Decorator Pattern)

 

我們都知道真實世界中需求異動是無法避免的,

好比一開始報表功能可能只有顯示和下載2大基本功能,

但後來使用者希望顯式報表時,要先檢查是否有權限,

又過沒多久使用者也希望下載前也要檢查權限,

在沒多久,使用者又希望權限檢查後,還要判斷系統日期是否為月底...等。

設計上你可能會利用物件導向繼承特性,

新增相關特定子類別(單一職責原則,提高內聚)繼承父類別,

然後特定子類別中撰寫特有方法,

但這樣的設計導致Client端需要知道該使用什麼樣的子類別(應該只能透過介面),

同時也違反LSP替換原則(使用父類別的地方都可以使用子類別取代,不需要任何改變),

那或許你又會說,那乾脆在父類別新增相關功能方法不就得了,

但如果使用者又打算取消某功能,

這樣你就得修改父類別(增加系統錯誤風險),

並進行重新編譯、測試...等一連串動作(這些動作看起來還真有些不合理),

所以為了要解決後續新增功能問題,

最好的方法就是不要修改已經通過測試的程式碼(符合開發封閉原則:對修改封閉,對擴充開放),

藉以降低系統可能的錯誤風險,這時設計就可以考慮使用裝飾模式來達到動態擴充相關功能(不採取繼承改採取組合)。

 

UML

image

 

 

需求:

檢查報表顯示和下載權限( rico 才有權限),且報表顯示後要確認系統日是否小於30號,

如果小於請警告使用者。

下面是利用裝飾模式的程式碼

 

報表執行如下

image

 

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      
    }

 

 

 

參考

Decorator pattern