解讀設計模式----工廠方法模式(FactoryMethod Pattern)
概述:
Factory Method模式是應用最爲廣泛的設計模式,畢竟他負責了一系列對象的創建,而對象的創建正是面向對象編程中最爲繁瑣的行爲。GOF在《設計模式》一書寫到,“Factory Method模式使一個類的實例化延遲到子類。”準確的說,Factory Method模式是將創建對象實例的責任,轉移到了工廠類中,並利用抽象原理,將實例化行爲延遲到具體工廠類。
意圖:
定義一個用於創建對象的接口,讓子類決定將哪一個類實例化。Factory Method模式使一個類的實例化延遲到子類。
UML圖:
常見的例子:
動物--貓、狗、牛、羊、豬........是人都知道,動物它們都是有形狀的吧,它們擁有各自不同的外觀(Display),是動物他就一定會吃東西(大多數動物還能吃,會吃...)。那具體他們是什麼形狀的呢?它們用怎麼樣的方法去吃東西呢?這不是我們所需要關心的,我們所關心的應該是怎麼通過工廠去創建具體動物的實例。
案例分析:
現在我們考慮一個日誌記錄的簡單例子,假定我們要設計一個日誌記錄組件,支持記錄的方法有記錄到文本文件(TxtWrite)和記錄到數據庫(DbWrite)兩種方式。在我門不考慮設計模式的情況下,這個日誌組件的簡單實現:
2using System.Collections.Generic;
3using System.Text;
4
5namespace DesignPattern.FactoryMethod
6{
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 Resolve resolve = new Resolve("txt");
12 resolve.WriteLog("Log message");
13 }
14 }
15
16 class Log
17 {
18 public static void TxtWrite(string message)
19 {
20 Console.WriteLine("TxtWrite():" + message);
21 }
22
23 public static void DbWrite(string message)
24 {
25 Console.WriteLine("DbWrite():" + message);
26 }
27 }
28
29 class Resolve
30 {
31 private string _type;
32 public Resolve(string type)
33 {
34 this._type = type;
35 }
36
37 public void WriteLog(string message)
38 {
39 if (this._type == "txt")
40 {
41 Log.TxtWrite(message);
42 }
43 else
44 {
45 Log.DbWrite(message);
46 }
47 }
48 }
49}
50
很顯然這是不符合我們的要求的,一擔需求改變,需要增加新的日誌記錄方式,那就必須得在Log類裏增加新的實現方法,而在Resolve類裏也同樣需要增加新的if(){}語句或是switch(){}語句來做判斷處理,這樣就引起了整個應用程序的不穩定。如果在深入分析,日誌被記錄到數據庫和文本文件是兩種不同的對象,這裏有必要、應該的是把他門分開做爲單獨的對象來處理。
2{
3 public static void TxtWrite(string message)
4 {
5 Console.WriteLine("TxtWrite():" + message);
6 }
7
8 public static void DbWrite(string message)
9 {
10 Console.WriteLine("DbWrite():" + message);
11 }
12}
其實我們可以進一步的爲這兩重不同的對象抽象出一個共性的父類出來。那到底該怎麼去抽象呢?我們在來看看下面這個示例,抽象出一個共性的父類AbstractLog(可把他當作是一個具體的"抽象"產品),結構圖如下:
2using System.Collections.Generic;
3using System.Text;
4
5namespace DesignPattern.FactoryMethod
6{
7 /// <summary>
8 /// 抽象"產品"
9 /// </summary>
10 public abstract class AbstractLog
11 {
12 public abstract List<Log> GetLog();
13 public abstract bool InsertLog(Log log);
14 }
15}
此時DbLog和TxtLog分別繼承父類AbstractLog,其定義如下:
2/// 將日誌記錄到數據庫
3/// </summary>
4public class DbLog:AbstractLog
5{
6 public override List<Log> GetLog()
7 {
8 List<Log> list = new List<Log>();
9 list.Add(new Log("被記錄到數據庫的日誌", DateTime.Now));
10 return list;
11 }
12
13 public override bool InsertLog(Log log)
14 {
15 //.實現略..將日誌記錄到數據庫
16 return true;
17 }
18}
19-----------------------------------------------
20/// <summary>
21/// 將日誌記錄到文本文件
22/// </summary>
23public class TxtLog:AbstractLog
24{
25 /// <summary>
26 /// 獲取日誌
27 /// </summary>
28 public override List<Log> GetLog()
29 {
30 List<Log> list = new List<Log>();
31 list.Add(new Log("被記錄到文本文件的日誌", DateTime.Now));
32 return list;
33 }
34
35 /// <summary>
36 /// 插入日誌
37 /// </summary>
38 public override bool InsertLog(Log log)
39 {
40 //.實現略..將日誌記錄到數據庫
41 return true;
42 }
43}
我們這樣做的好處在哪呢?其實仔細觀察就會發現,如果我們現在需要增加把日誌記錄到XML文件去,那該怎麼處理呢?其實只需要新添一個新類並繼承於AbstractLog並實現其方法既可,這樣既滿足了類之間的層次關係,還更好的符合了面向對象設計中的單一職責原則,每一個類都只負責一件具體的事情。
到這裏我們的設計可說是完美的了,根據多態原理,我們完全可以像下面這樣來調用:
2al.GetLog();
3//
4AbstractLog al1 = new TxtLog();
5al1.GetLog();
6//.
從上面我們不難看出,如果需要改DbLog爲TxtLog就必須得new TxtLog(),這樣的做法使程序顯得很死板,對象的創建不夠靈活。此時就需要解耦具體的日誌記錄方式和應用程序。這就要引入Factory Method模式了,每一個日誌記錄的對象就是工廠所生成的產品,既然有兩種記錄方式,那就需要兩個不同的工廠去生產了,由於每一個日誌的對象所對應的工廠都是負責這個日誌對象的創建工作,這裏我們是不是又應該抽象出工廠的共性呢?顯然答案是肯定有必要的,結構圖如下:
代碼如下:
2{
3 /// <summary>
4 /// 抽象Log工廠(Creator)
5 /// </summary>
6 public abstract class AbstractLogFactory
7 {
8 public abstract AbstractLog CreateLog();
9 }
10}
2{
3 public override AbstractLog CreateLog()
4 {
5 return new DbLog();
6 }
7}
8----------------------------------------------
9public class TxtLogFactory:AbstractLogFactory
10{
11 public override AbstractLog CreateLog()
12 {
13 return new TxtLog();
14 }
15}
通過工廠方法模式我們把上面那對象創建工作封裝在了工廠中,此時似乎完成了整個Factory Method的過程。我們先看看客戶斷的調用,代碼如下:
2{
3 AbstractLogFactory alf = new DbLogFactory();
4 AbstractLog al = alf.CreateLog();
5
6 //al.InsertLog(new Log("到數據庫", DateTime.Now));
7 Console.WriteLine("日誌內容:" + al.GetLog()[0].Message + " 日誌記錄時間:" + al.GetLog()[0].RecordTime);
8
9 AbstractLogFactory alf2 = new TxtLogFactory();
10 AbstractLog al2 = alf2.CreateLog();
11 Console.WriteLine("日誌內容:" + al2.GetLog()[0].Message + "日誌記錄時間:" + al2.GetLog()[0].RecordTime);
12}
仔細觀察上面調用代碼就會發現,客戶程序中,我們有效地避免了具體產品對象和應用程序之間的耦合,可是我們也看到,增加了具體工廠對象和應用程序之間的耦合。那這樣究竟帶來什麼好處呢?在程序中AbstractLog對象的創建是頻繁的,要創建具體的類型決定於AbstractLogFactory alf=new DbLogFactory()/TxtLogFactory();此時,我們仍然爲具體工廠對象的創建而憂心沖沖。
當我們要改變日誌記錄方式的時候還是得修改此處的代碼,這樣顯然也不夠靈活。此時,我們可以利用.NET的特性,我們可以避免這種不必要的修改。下面我們利用.NET中的反射機制來進一步修改我們的程序,這時就要用到配置文件了,如果我們想使用哪一種日誌記錄方式,則在相應的配置文件中設置如下:
2<configuration>
3 <appSettings>
4 <add key="factoryName" value="DbLogFactory"/>
5 </appSettings>
6</configuration>
通過上面配置文件和反射機制對工廠模式的擴展,這樣創建具體工廠就變得更加靈活了。要創建指定的工廠就不在依賴與代碼不需要在去修改調用端的代碼實現了,只需要修改配置文件的配置參數既可。如果現在需要增加新的日誌記錄方法,我們就只需要增加一個新的類去實現AbstractLog和一個新的日誌記錄方法的工廠去實現AbstractLogFactory既可,我們不在需要去修改具體的工廠類。這樣很好的符合了開放封閉原則。通過改善過後的客戶端調用:
2{
3 string factoryName = ConfigurationManager.AppSettings["factoryName"];
4 string assemblyName="DesignPattern.FactoryMethod";
5 string className="DesignPattern.FactoryMethod."+factoryName;
6 AbstractLogFactory alf = (AbstractLogFactory)Assembly.Load(assemblyName).CreateInstance(className);
7 AbstractLog al = alf.CreateLog();
8 Console.WriteLine("日誌內容:" + al.GetLog()[0].Message + " 日誌記錄時間:" + al.GetLog()[0].RecordTime);
9}
上面的的調用方式還是顯得有點羅嗦,其實我們完全可以把具體工廠對象的創建工作封裝到一個類裏去,在此就不作詳細解說了。
工廠方法模式總結:
Factory Method模式是設計模式中應用最爲廣泛的模式,通過本文,相信大家已經對它有了一定的認識。然而我們要明確的是:在面向對象的編程中,對象的創建工作非常簡單,對象的創建時機卻很重要。Factory
Method要解決的就是對象的創建時機問題,它提供了一種擴展的策略,很好地符合了開放封閉原則。______________________________________________________________________________________________
本文示例代碼下載:DesignPattern.FactoryMethod.rar
本文參考資料:
GOF----《設計模式》
Bruce Zhang----《軟件設計精要與模式》
http://www.dofactory.com/