工廠模式
當系統中有多個類似的物件,而你隨時可能要建立這些物件的實體,但是到底要 new 哪一個物件,卻必須等到執行階段才能知道,這時候就適合使用工廠模式。 這個模式利用一個稱為 Factory 的元素來執行實際建立實體的工作,同時也讓程式架構符合「開放-封閉原則」(Open/Closed Principle, OCP)。
一般情況
在一般情況下,我們要建立物件實體,只要透過"new"就可以了,如下面程式碼範例。 這一段程程式碼建立一個 DailyTrade 物件,可以自網路上下載收盤交易資訊;同時也建立一個 BigTrade 物件,可以自網路上下載巨額交易資訊。 你可能設計了許多類似的物件,可以用來下載不同類型資訊,因此你都必須先 new 該物件的實體才能進行動作,可是如果需求必須等到執行階段才決定要 new 哪一個物件實體的話,那麼這種做法將會變的很難管理。
public interface ITradeProvider { void DownloadData(); void ImportData(); } public class DailyTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download DailyTrade"); } public void ImportData() { Console.WriteLine("Import DailyTrade"); } } public class BigTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download BigTrade"); } public void ImportData() { Console.WriteLine("Import BigTrade"); } }
ITradeProvider trade = null; trade = new DailyTrade(); trade.DownloadData(); trade.ImportData(); trade = new BigTrade(); trade.DownloadData(); trade.ImportData();
簡單工廠模式(Simple Factory Pattern)
簡單工廠模式將類別的實體化動作封裝在 Factory 內,讓程式可以在執行時才決定要實體化哪一個類別,如下圖所示。
public interface ITradeProvider { void DownloadData(); void ImportData(); } public class DailyTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download DailyTrade"); } public void ImportData() { Console.WriteLine("Import DailyTrade"); } } public class BigTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download BigTrade"); } public void ImportData() { Console.WriteLine("Import BigTrade"); } } public class Fctory { public static ITradeProvider CreateProduct(string TypeName) { ITradeProvider obj = null; switch (TypeName) { case "DailyTrade": obj = new DailyTrade(); break; case "BigTrade": obj = new BigTrade(); break; default: throw new Exception("type undefined.."); } return obj; } }
ITradeProvider trade = null; trade = Fctory.CreateProduct("DailyTrade"); trade.DownloadData(); trade.ImportData(); trade = Fctory.CreateProduct("BigTrade"); trade.DownloadData(); trade.ImportData();
上面程式碼可以看的出來,當 Client 在建立物件實體時,可以利用一個字串變數,就可以在執行階段決定要實體化的類別。
使用 Reflection 設定 Factory
上述的 Fctory 類別中,使用 switch case 用來判斷要建立的實體類型,如果系統新增一個新的物件,那麼這段程式碼也就必須加以修改,這還是違反了開放-封閉原則。 這時候可以利用 Reflection 機制,直接依物件名稱來建立物件實體,底下示範如何使用 Reflection 建立實體。
public class Fctory2 { private static readonly string AssemblyName = "DesignPatterns"; private static readonly string Namespace = "FactoryPattern.SimpleFacoryPattern"; public static ITradeProvider CreateProduct(string TypeName) { string className = AssemblyName + "." + Namespace + "." + TypeName; return (ITradeProvider)Assembly.Load(AssemblyName).CreateInstance(className); } }
工廠方法模式(Factory Method Pattern)
雖然在上面的簡單工廠模式中,利用 Reflection 機制來修改 Factory 以達到 OCP 原則,但是這個做法僅限程式語言有 Reflection 機制才行。
所以,如果要滿足程式架構的 OCP 原則,就要利用「工廠方法模式」來解決,它的做法是將 Factory 類別抽象化,讓每個 Product 子類別都有屬於自己的工廠類別。 它的優點是讓程式可以容易擴充新的功能,但是不用去修改到舊有的程式碼。如下圖所示。
public interface ITradeProvider { void DownloadData(); void ImportData(); } public class DailyTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download DailyTrade"); } public void ImportData() { Console.WriteLine("Import DailyTrade"); } } public class BigTrade : ITradeProvider { public void DownloadData() { Console.WriteLine("Download BigTrade"); } public void ImportData() { Console.WriteLine("Import BigTrade"); } } public interface IFctory { ITradeProvider CreateProduct(); } public class DailyTradeFctory : IFctory { public ITradeProvider CreateProduct() { return new DailyTrade(); } } public class BigTradeFctory : IFctory { public ITradeProvider CreateProduct() { return new BigTrade(); } }
ITradeProvider trade = null; IFctory factory1 = new DailyTradeFctory(); trade = factory1.CreateProduct(); trade.DownloadData(); trade.ImportData(); IFctory factory2 = new BigTradeFctory(); trade = factory2.CreateProduct(); trade.DownloadData(); trade.ImportData();
「工廠方法模式」解決了「簡單工廠模式」的擴充功能,當有新的物件類型要增加時(例如新的 FBTrade),只需要加入一個 FBTrade 和 FBTradeFactory 類別即可,而不用去修改舊有的程式碼。
抽象工廠模式(Abstract Factory Pattern)
假設我們系統需求要設計一個交易資訊下載,包含上市股票的每日交易資訊,巨額交易,融資融券,法人交易。 如果現在又多了另一組資料來源,叫做上櫃股票,同樣包含這些資訊,而且可能日後還有另外的資料來源,那麼這時候就可能使用「抽象工廠模式」來設計這樣子的需求。
public interface IDailyTradeProvider { void DownloadData(); void ImportData(); } public interface IBigTradeProvider { void DownloadData(); void ImportData(); } public interface IAbstractFactory { IDailyTradeProvider getDailyTrade(); IBigTradeProvider getBigTrade(); } // 第一組 public class TweDailyTrade : IDailyTradeProvider { public void DownloadData() { Console.WriteLine("Download Twe DailyTrade"); } public void ImportData() { Console.WriteLine("Import Twe DailyTrade"); } } public class TweBigTrade : IBigTradeProvider { public void DownloadData() { Console.WriteLine("Download Twe BigTrade"); } public void ImportData() { Console.WriteLine("Import Twe BigTrade"); } } public class TweTradeFactory : IAbstractFactory { public IDailyTradeProvider getDailyTrade() { return new TweDailyTrade(); } public IBigTradeProvider getBigTrade() { return new TweBigTrade(); } } // 第二組 public class OtcDailyTrade : IDailyTradeProvider { public void DownloadData() { Console.WriteLine("Download Otc DailyTrade"); } public void ImportData() { Console.WriteLine("Import Otc DailyTrade"); } } public class OtcBigTrade : IBigTradeProvider { public void DownloadData() { Console.WriteLine("Download Otc BigTrade"); } public void ImportData() { Console.WriteLine("Import Otc BigTrade"); } } public class OtcTradeFactory : IAbstractFactory { public IDailyTradeProvider getDailyTrade() { return new OtcDailyTrade(); } public IBigTradeProvider getBigTrade() { return new OtcBigTrade(); } }
IAbstractFactory factoy; IDailyTradeProvider tradeA; IBigTradeProvider tradeB; factoy = new TweTradeFactory(); tradeA = factoy.getDailyTrade(); tradeA.DownloadData(); tradeA.ImportData(); tradeB = factoy.getBigTrade(); tradeB.DownloadData(); tradeB.ImportData(); factoy = new OtcTradeFactory(); // 底下這段程式和上面一段一模一樣,但是得到結果是完全不一樣的, // 這個模式很方便將整組物件一起抽換的情境。 tradeA = factoy.getDailyTrade(); tradeA.DownloadData(); tradeA.ImportData(); tradeB = factoy.getBigTrade(); tradeB.DownloadData(); tradeB.ImportData();
沒有留言:
張貼留言