[Design Patterns]使用Interface來實作Template Method Pattern

  • 7259
  • 0

[Design Patterns]使用Interface來實作Template Method Pattern

前言
之前許多篇文章已經有舉Template Method,例如:
[ASP.NET]91之ASP.NET由淺入深 不負責講座 Day15 – Abstract與Interface,這邊快速的用一個例子再說明一次:

宣告一個abstract的class為『AbstractRD』,有一個Template Method為TestDrivenDesign()。

    public abstract class AbstractRD
    {
        /// <summary>
        /// RD的Template Method:TestDrivenDesign()
        /// 繼承AbstractRD的concrete class都可以直接被呼叫AbstractRD的TestDrivenDesign()
        /// </summary>
        public void TestDrivenDesign()
        {
            //先寫測試
            this.WriteTestCode();
            //驗證測試
            this.Verify();
            //開發, 修改程式
            this.Design();
            //驗證測試
            this.Verify();
            //重構程式
            this.Refactoring();
            //驗證測試
            this.Verify();
        }

        protected abstract void WriteTestCode();
        protected abstract void Verify();
        protected abstract void Design();
        protected abstract void Refactoring();
    }


有兩個class,分別為Joey與Rico,繼承AbstractRD。

    public class Joey : AbstractRD
    {
        private const string name = "91";
        protected override void WriteTestCode()
        {
            Console.WriteLine("{0} 撰寫測試中", name);
        }

        protected override void Verify()
        {
            Console.WriteLine("{0} 測試中", name);
        }

        protected override void Design()
        {
            Console.WriteLine("{0} 開發中", name);
        }

        protected override void Refactoring()
        {
            Console.WriteLine("{0} 重構中", name);
        }
    }

 

    public class Rico : AbstractRD
    {
        private const string name = "Rico";
        protected override void WriteTestCode()
        {
            Console.WriteLine("{0} 撰寫測試中", name);
        }

        protected override void Verify()
        {
            Console.WriteLine("{0} 測試中", name);
        }

        protected override void Design()
        {
            Console.WriteLine("{0} 開發中", name);
        }

        protected override void Refactoring()
        {
            Console.WriteLine("{0} 重構中", name);
        }
    }


Client端的呼叫:

    class Program
    {
        static void Main(string[] args)
        {
            AbstractRD joey = new Joey();
            joey.TestDrivenDesign();

            Console.WriteLine("");

            AbstractRD rico = new Rico();
            rico.TestDrivenDesign();
            Console.WriteLine("");
        }
    }


看一下執行結果:
image

這就是標準的Template Method,把TDD的方法做成一個罐頭。透過abstract class,在對外的方法中呼叫abstract的function,讓子類別也都可以使用這樣的Template Method,但Method的內容是每個子類別自行override abstract的function。

因為Template Method需要方法內容來控制Template Method的流程,所以需使用abstract class,而無法使用Interface。因為Interface無法定義方法內容。

疑問
實際的狀況,Rico可是跨領域的高手。假設有另一個屬於DBA的Template Method要實作,那Abstract class如下所示:

    public abstract class AbstractDBA
    {
        public void ReleaseDeadLock()
        {
            //偵測哪裡有deadlock
            this.DetectDeadLock();
            //解開deadlock
            this.KillDeadLock();
            //通知RD已經解除deadlock
            this.NotifyRD();
        }

        protected abstract void DetectDeadLock();
        protected abstract void KillDeadLock();
        protected abstract void NotifyRD();
    }


這個時候,Rico可能是身兼RD與DBA,但C#無法多重繼承,一次只能繼承一個父類別,該怎麼讓Rico也可以用AbstractDBA的ReleaseDeadLock呢?

實作
有了這樣的疑問,我就在想怎麼用別的方式來讓子類別可以套用多個Template Method。Abstract會有一次只能繼承一個的問題,Interface則可以一次實作多個介面。但Interface無法擁有方法內容來製作Template Method。

所以我的選擇是,Interface+Extension Method,來讓Interface可以有內容。

步驟
1. 定義一個介面是IDBA,把原本AbstractDBA的abstract void,放到IDBA裡面。

    public interface IDBA
    {
        void DetectDeadLock();
        void KillDeadLock();
        void NotifyRD();
    }


2. 宣告一個static的class,裡面宣告一個Extension Method,針對的型別為IDBA,並將原本的Template Method內容放進去Extension Method裡面。

    public static class ExtensionMethodOfDBA
    {
        public static void ReleaseDeadLock(this IDBA dba)
        {
            //偵測哪裡有deadlock
            dba.DetectDeadLock();
            //解開deadlock
            dba.KillDeadLock();
            //通知RD已經解除deadlock
            dba.NotifyRD();
        }
    }


3. 讓Rico實作IDBA,撰寫Template Method所需要的方法內容(原abstract void)

    public class Rico : AbstractRD, IDBA
    {
        private const string name = "Rico";

        #region AbstractRD
        protected override void WriteTestCode()
        {
            Console.WriteLine("{0} 撰寫測試中", name);
        }

        protected override void Verify()
        {
            Console.WriteLine("{0} 測試中", name);
        }

        protected override void Design()
        {
            Console.WriteLine("{0} 開發中", name);
        }

        protected override void Refactoring()
        {
            Console.WriteLine("{0} 重構中", name);
        }
        #endregion
        
        #region IDBA
        public void DetectDeadLock()
        {
            Console.WriteLine("{0} 偵測dead lock中", name);
        }

        public void KillDeadLock()
        {
            Console.WriteLine("{0} 解除dead lock中", name);
        }

        public void NotifyRD()
        {
            Console.WriteLine("{0} 通知RD中", name);
        }
        #endregion
        
    }


4. Client端加上呼叫由Rico實作的IDBA上的Template Method

    class Program
    {
        static void Main(string[] args)
        {
            AbstractRD joey = new Joey();
            joey.TestDrivenDesign();

            Console.WriteLine("");

            AbstractRD rico = new Rico();
            rico.TestDrivenDesign();
            Console.WriteLine("");

            IDBA dba = new Rico();
            dba.ReleaseDeadLock();
            Console.WriteLine("");
        }
    }

執行結果
image

依此類推,假設Derrick也是DBA,套用這樣的方式:
image

image

執行結果
image 

如此一來,只需要把AbstractRD,也改成interface的方式,就可以不必侷限於Template Method只能繼承一個abstract class的限制了。

結論
有這樣的方式,如果是像小朱這樣的高手,就可以實作IRD, IDBA, IMVP, ISE...

不過這個設計方法也不是完美的,因為interface上的方法都public出來了,原本在abstract中是可以將這些方法封裝隱藏於class中,只開放Template Method給外界呼叫。要將這些方法隱藏起來,就需要其他手段或是結合其他Pattern的作法來修飾了。

這一篇文章只是根據System.Linq底下的Enumerable class,裡面針對IEnumerable<T>所設計許多的擴充方法(常用的Select(), Where()等方法的原理),加以應用與練習一下。拿來當Template Method的變形,也推翻了我原本以為Template Method,只有abstract class作的到,interface作不到的想法。


blog 與課程更新內容,請前往新站位置:http://tdd.best/