1. 簡介
工廠方法模式(Factory Method Pattern)也稱為工廠模式,又稱為虛擬構造器模式或多態模式。
在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
工廠方法模式主要類:
-
Product抽象產品類(或是接口),派生出所有的具體產品類
ConcreteProductA
、ConcreteProductA
…… -
ConcreteProduct 具體產品類,繼承於
Product
抽象類 -
Factory 抽象工廠接口,所有的具體工廠類都是實現該接口
-
ConcreteFactory 具體工廠,實現了
Factory
接口,創建具體的產品對象
2. 示例1-計算器重構
2.1 背景說明
在設計模式——簡單工廠模式一文中,使用簡單工廠模式創建了一個簡單的四則計算器,
我們需要一個抽象的Operation父類,派生出四個加減乘除子類
在工廠中根據傳入的符號參數,生成不同的運行類。
但是問題也就來了,若是添加一個新的運行類,我們依舊可以繼承於Operation抽象父類,override運算方法,然后在工廠中添加一個新的分支,創建該運算類。
那么問題就來了!按照設計原則——開閉原則,我們應該開放擴展,關閉修改。在這里,我們擴展了運算方法,但是同時也對Factory類進行了修改,這明顯不符合開閉原則啊!
其實這就是簡單工廠模式的缺點。
為了解決這個缺點,我們引入工廠方法模式。(事實上,簡單工廠模式是工廠方法模式的簡化版,而不是因為簡單工廠模式有缺點才演變為工廠方法模式的)
我們對在簡單工廠模式中實現的計算器進行進一步的重構
2.2 代碼重構
提取出工廠接口:IFactory
,分別創建每個運行類的工廠類AddFactory
,SubFactory
,MulFactory
,DivFactory
。
//工廠接口:抽象工廠
public interface IFactory
{
Operation CreateOperation();
}
//加法運行工廠:具體工廠
public class AddFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationAdd();
}
}
//減法運行工廠:具體工廠
public class SubFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationSub();
}
}
//乘法運行工廠:具體工廠
public class MulFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationMul();
}
}
//除法運行工廠:具體工廠
public class DivFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationDiv();
}
}
這時我們在客戶端,可以這樣使用:
static void Main(string[] args)
{
//創建一個具體的工廠對象:加法工廠對象
IFactory addFactory = new AddFactory();
//使用加法工廠對象創建加法運算類
Operation addOper = addFactory.CreateOperation();
addOper.NumA = 2;
addOper.NumB = 3;
Console.WriteLine(addOper.GetResult());//print:5
Console.ReadKey();
}
其實到這里是可以發現,經過重構后的代碼,每個具體運算類都有一個相應的工廠類
若是需要添加一個新的運行符,則我們只需要創建一個新的具體工廠類XXXFactory
,實現抽象工廠接口IFactory
,同時再創建新的運算符實現類XXX
,繼承於運算抽象父類Operation
。這樣就可以在不修改程序中的現有的類的情形下,實現對程序的擴展!滿足了開閉原則,避免了簡單工廠模式的缺點!
工廠方法模式實現時,客戶端需要決定實例化哪一個工廠來實現運算類,選擇判斷的問題還是存在的,也就是說,工廠方法把簡單工廠的內部邏輯判斷移到了客戶端代碼來進行。你想要加功能,本來是改工廠類的,而現在是修改客戶端
2.3 程序類圖
3. 示例2-模擬多功能日記記錄器
3.1 背景說明
示例來源於《設計模式實訓-第二版》
某系統日志記錄器要求支持多種日志記錄方式,如文件日志記錄(FileLog)、數據庫日志記錄(DatabaseLog)等,且用戶可以根據要求動態選擇日志記錄方式,現使用工廠方法模式設計該系統。
這里我為什么要用這個示例? 因為在實際開發中,一些框架和程序包都是按照工廠方法模式開發的,包括日志框架。
其實你想一想,使用的一些框架和程序包的調用,是不是都先是創建一個ConcreteFactory對象(或者Creator對象),之后使用該具體工廠對象創建ConcreteProduct對象
3.2 代碼實現
①抽象產品和具體產品的實現代碼
//抽象產品:日志記錄器總接口(使用抽象類也可以)
public interface ILog
{
void WriteLog();
}
//具體產品:文件日志記錄器
public class FileLog : ILog
{
public void WriteLog()
{
Console.WriteLine("記錄日志於日志文件中");
}
}
//具體產品:數據庫日志記錄器
public class DatabaseLog : ILog
{
public void WriteLog()
{
Console.WriteLine("記錄日志於日志數據庫中");
}
}
②抽象工廠和具體工廠的實現代碼
//抽象工廠:日志記錄器工廠
public interface ILogFactory
{
ILog CreateLog();
}
//具體工廠:文件日記記錄器工廠
public class FileLogFactory : ILogFactory
{
public ILog CreateLog()
{
return new FileLog();
}
}
//具體工廠:數據庫日記記錄器工廠
public class DatabaseLogFactory : ILogFactory
{
public ILog CreateLog()
{
return new DatabaseLog();
}
}
③客戶端代碼
class Program
{
static void Main(string[] args)
{
//創建一個具體的工廠對象:FileLogFactory
ILogFactory logFac = new FileLogFactory();
//由工廠對象,創建產品對象:FileLog
//FileLog log = logFac.CreateLog() as FileLog;
ILog log = logFac.CreateLog();
log.WriteLog();//print:記日志於日志文件中
Console.ReadKey();
}
}
3.3 程序類圖
4. 總結分析
4.1 優點
-
當調用者需要一個具體產品對象,只要使用該具體產品的具體工廠創建該對象即可。調用者不需要知道具體產品對象是怎么創建的
-
便於擴展:使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”。
-
工廠方法模式是平行的類層次結構
-
什么是平行的類層次結構?簡單的說,假如有兩個類的層次,其中一個層次中的類在另外一個層次中都有相對應的類,則稱這兩個類層次是平行的類層次結構。工廠方法模式就是平行的類層次結構。這里可以看UML圖,非常明顯!工廠類層次和產品類層次就是平行的。
-
這種平行的類層次結構用來干什么呢?主要用來把一個類層次中的某些行為分離出來,讓類層次中的類把原本屬於自己的職責,委托給分離出來的類去實現,從而使得類層次本身變得更簡單,更容易擴展和復用。
-
4.2 缺點
- 擴展系統的時候,一旦添加一個具體產品類,則必須同時添加一個相應的具體工廠類。所以系統中類的添加是成對的添加,一定程度上造成系統繁雜。
4.3 適應場合
-
工廠方法模式是new一個對象的替代品。所以其實在需要大量實例化對象的地方都是可以使用的。
-
實際中開發中,工廠方法主要用於工具包和框架中
4.4 其他說明
-
為什么工廠方法模式也稱為多態工廠模式?工廠接口的有不同的實現(也就是多態),換一句說也就是具體工廠類都有同一個工廠接口。
-
工廠方法模式的簡化:若是產品對象較少,可以使用一個具體工廠類(包含一個靜態的工廠方法)根據參數去創建所有的具體產品,這就是所謂的簡單工廠模式。
-
注意簡單工廠模式就是通過參數化工廠方法實現的。參數化工廠方法具體指:通過給工廠方法傳遞參數,讓工廠方法根據參數創建不同的產品對象。
-
詳細可參考《研磨設計模式》
-