1. 程式人生 > WINDOWS開發 >C#中的TemplateMethod模式

C#中的TemplateMethod模式

一個真實的故事

大學的時候就開過一門課程,講設計模式,可是大學生沒什麼程式設計實踐經驗,在大學裡面聽設計模式的感覺,就像聽天書。聽著都有道理,可是完全領會不到其中的奧妙,大抵原因就在於沒有走過彎路,沒有吃過設計不當的虧。古人云,“操千曲而後曉聲,觀千劍而後識器”,誠不欺我。
?
博主在之前的某個專案中,設計出了一些工具類,像屬性視窗,錯誤提示視窗,還有一個視窗管理類管理它們,當時我實現工具儲存時候的程式碼是這樣的:

    class WindowManager
    {
        private List<ITool> _Tools = new List<ITool>();        

        public void AddTool(ITool tool)
        {
            _Tools.Add(tool);
        }

        public void SaveAllTools()
        {
            foreach(var tool in _Tools)
            {
                tool.Save();
            }
        }
    }

    interface ITool
    {
        bool BeforeSave();
        void Save();
        void AfterSave();
    }

    class PropertyWindow : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

    class ErrorLis : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

當時博主對這段程式碼還挺滿意,完全沒有看出這兒有什麼問題,覺得這簡直寫的太OO了,有類,有介面,有針對介面程式設計,至於新加的工具類,也不會影響原來的程式碼,簡直太符合開閉原則了。老鐵,沒毛病!
?
好日子就這麼繼續下去,每當需要新新增一個工具,我就新加一個類,在類裡面實現Save的邏輯,直到有一天,添加了一個ResourceControl

    class ResourceControl : ITool
    {
        public bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        public void Save()
        {
            if (!BeforeSave())
            {
                //do save
                AfterSave();
            }
        }

        public void AfterSave()
        {

        }
    }

?
在它的save裡面,我把if(BeforeSave())寫成了if(!BeforeSave())。。。
於是,我又額外花了一些時間來找到這個問題,修改它並在下次新增新類的時候戰戰兢兢提醒自己不要犯這種低階的錯誤。那麼,我們有沒有好的辦法來解決這個問題呢?

問題分析

其實就算每次新增新類的時候我們都能仔細的小心避免維護相同的邏輯,這段程式碼的設計也還是有可以改進的地方,比如,BeforeSave和AfterSave在這裡作為介面ITool的一部分而公開,意味著客戶程式碼可以自由的呼叫BeforeSave和AfterSave,然而這很可能並不是程式碼作者的本意,畢竟,不呼叫Save而單獨呼叫BeforeSave和AfterSave有什麼意義呢?讓客戶能夠看到更多不必要的方法,增加了客戶錯誤使用介面的可能性,不是麼?
?
綜上所述,我們需要解決的問題如下:

  • 抽象出Save,BeforeSave和AfterSave的邏輯關係,在一個地方固定下來,確保新增加的類所實現的這三個方法,都能自動具有這種邏輯關係。
  • 對客戶程式碼隱藏不必要的介面。
    ?
    這種場景下面,我們需要用到設計模式中的TemplateMethod(模版方法)模式。
    ?

TemplateMethod模式

WIKI上面,TemplateMethod模式的定義如下,
In software engineering,the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in an operation,deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm‘s structure.

大概意思就是,模版方法模式是一種行為類設計模式,允許軟體在更高的層次定義程式骨架,但是可以在子類推遲實現某些步驟。
?
類圖如下:
技術分享圖片

這完全符合我們的需求,讓我們試著修改我們的程式碼。
?

使用TemplateMethod重新實現的程式碼

    class WindowManager
    {
        private List<AbstractTool> _Tools = new List<AbstractTool>();        

        public void AddTool(AbstractTool tool)
        {
            _Tools.Add(tool);
        }

        public void SaveAllTools()
        {
            foreach(var tool in _Tools)
            {
                tool.Save();
            }
        }
    }

    abstract class AbstractTool
    {
        protected abstract bool BeforeSave();
        protected abstract void DoSave();
        protected abstract void AfterSave();
        public void Save()
        {
            if(!BeforeSave())
            {
                DoSave();
                AfterSave();
            }

        }        
    }

    class PropertyWindow : AbstractTool
    {
        protected override bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        protected override void DoSave()
        {
            
        }

        protected override void AfterSave()
        {

        }
    }

    class ErrorLis : AbstractTool
    {
        protected override bool BeforeSave()
        {
            //do something specific here
            return true;
        }

        protected override void DoSave()
        {

        }

        protected override void AfterSave()
        {

        }
    }

從上面我們可以看到,我們用一個抽象類AbstractTool代替之前的ITool介面,抽象類和介面的一個區別就是,抽象類可以在其中嵌入某些邏輯,所以我們在Save這個公共的非虛方法中,完全實現了我們的BeforeSave和AfterSave邏輯,僅僅留下了BeforeSave,AfterSave和DoSave給子類覆蓋。這樣我們得到的好處是:

  • 抽象類只公開了一個Save方法,所以客戶程式碼不用擔心會呼叫其他錯誤的方法。
  • 抽象類完全固定了Save邏輯,先呼叫BeforeSave檢查,之後執行DoSave進行具體的Save事項,最後進行AfterSave行為。子類只需要重新依據子類的需求覆蓋這三個虛方法即可。新新增的工具類,只要覆蓋這三個虛方法,至於虛方法之間的邏輯,抽象類已經固定,不用擔心。

結論

“紙上得來終覺淺,絕知此事要躬行”,祖宗的話,不會錯的,如果沒有一定的程式設計實踐和總結,是沒有辦法領悟設計模式的,博主也是通過之前那個例子才領悟到TemplateMethod模式的妙用。希望大家多多程式設計,早日領悟。