[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("");
}
}
看一下執行結果:
這就是標準的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("");
}
}
執行結果
依此類推,假設Derrick也是DBA,套用這樣的方式:
執行結果
如此一來,只需要把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/