工廠模式
當系統中有多個類似的物件,而你隨時可能要建立這些物件的實體,但是到底要 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();

沒有留言:
張貼留言