簡單工廠模式是一個工廠類根據工廠方法的參數創建不出不同的產品, 工廠方法模式是每一個產品都有一個一一對應的工廠負責創建該產品。那麼今天要講的抽象工廠模式是一個工廠能夠產生關聯的一系列產品。抽象工廠模式相對於簡單工廠和工廠方法模式來著更具抽象性。 一、抽象工廠模式演繹 我們先來看一個簡單的需求: 甲方 ...
簡單工廠模式是一個工廠類根據工廠方法的參數創建不出不同的產品, 工廠方法模式是每一個產品都有一個一一對應的工廠負責創建該產品。那麼今天要講的抽象工廠模式是一個工廠能夠產生關聯的一系列產品。抽象工廠模式相對於簡單工廠和工廠方法模式來著更具抽象性。
一、抽象工廠模式演繹
我們先來看一個簡單的需求: 甲方要開發一套辦公自動化軟體,其中有一個非常重要的功能就是要能夠導入Word 文檔和Excel 文檔。
開發人員拿到需求後就開始編碼了, 很快代碼寫完了:
public class ImportTool { public void ImportWord() { Console.WriteLine("Import Word"); } public void ImportExcel() { Console.WriteLine("Import Excel"); } }
客戶端調用代碼:
class Program { static void Main(string[] args) { ImportTool importTool = new ImportTool(); importTool.ImportWord(); importTool.ImportExcel(); Console.ReadKey(); } }
輸出結果:
看起來不錯, 但是看代碼,客戶端的代碼和具體的實現之間是直接new出來的 ,簡直就是面向具體編程,沒有介面沒有抽象,是不是違背了ISP原則了?,那好開發人員決定提出一個抽象層,提出一個抽象的文檔介面, 用簡單工廠模式來實現這個需求:
UML 圖如下:
代碼如下:
public interface IDocument { void Import(); } public class WordDocument : IDocument { public void Import() { Console.WriteLine("Import Word"); } } public class ExcelDocument : IDocument { public void Import() { Console.WriteLine("Import Excel"); } } public class DocumentFactory { public static IDocument Create(string documentType) { IDocument document; switch (documentType) { case "word": document = new WordDocument(); break; case "excel": document = new ExcelDocument(); break; default: throw new ArgumentException("Invalid argument: documentType"); } return document; } }
客戶端調用:
static void Main(string[] args) { IDocument document=DocumentFactory.Create("word"); document.Import(); document=DocumentFactory.Create("excel"); document.Import(); Console.ReadKey(); }
輸出:
看起來完美這是一個標準的靜態工廠模式的實現。
第一次需求變更: 增加 對Power Point 的導入支持
增加了一個產品相當於,因為之前應用了簡單工廠模式現在改起來很簡單。
UML 圖:
代碼:
public interface IDocument { void Import(); } public class WordDocument : IDocument { public void Import() { Console.WriteLine("Import Word"); } } public class ExcelDocument : IDocument { public void Import() { Console.WriteLine("Import Excel"); } } public class PowerPointDocument : IDocument { public void Import() { Console.WriteLine("Import Power Point"); } } public class DocumentFactory { public static IDocument Create(string documentType) { IDocument document; switch (documentType) { case "word": document = new WordDocument(); break; case "excel": document = new ExcelDocument(); break; case "powerpoint": document = new PowerPointDocument(); break; default: throw new ArgumentException("Invalid argument: documentType"); } return document; } }
客戶端調用:
static void Main(string[] args) { IDocument document=DocumentFactory.Create("word"); document.Import(); document=DocumentFactory.Create("excel"); document.Import(); document = DocumentFactory.Create("powerpoint"); document.Import(); Console.ReadKey(); }
輸出結果:
沒問題,一起都在控制之中。
第二次需求變更: 支持office 2007 以後的文檔格式。
Offcie 2007是個坎,2007之前的文檔格式和2007以後的文檔格式不一 樣, word 在2007前的文檔尾碼是.doc, 2007 及以後就變成.docx了這個看似簡單的需求實則是增加了小一半的工作量啊,新的格式的文檔要重新進行解碼才能拿到正確的數據,拿剛剛實現的簡單工廠設計模式來應對這次變更就要新增加3個產品類並且要工廠來創建創建者三個類的實例。看來靜態工廠已經不能再適應這一次的需求變化了,會導致靜態工廠方法的邏輯變得異常複雜難以維護。那用工廠方法模式來解決這個問題,工廠方法剛好可以將產品的創建工作提取到單獨的工廠中去完成,重構下工廠將靜態工廠模式替換成工廠方法模式:
UML 圖:
public interface IDocument { void Import(); } public interface IDocumentFactory { IDocument Create(); } public class WordDocument : IDocument { public void Import() { Console.WriteLine("Import Word"); } } public class ExcelDocument : IDocument { public void Import() { Console.WriteLine("Import Excel"); } } public class PowerPointDocument : IDocument { public void Import() { Console.WriteLine("Import Power Point"); } } public class WordXDocument : IDocument { public void Import() { Console.WriteLine("Import WordX"); } } public class ExcelXDocument : IDocument { public void Import() { Console.WriteLine("Import ExcelX"); } } public class PowerPointXDocument : IDocument { public void Import() { Console.WriteLine("Import Power PointX"); } } public class WordDocumentFactory : IDocumentFactory { public IDocument Create() { return new WordDocument(); } } public class WordXDocumentFactory : IDocumentFactory { public IDocument Create() { return new WordXDocument(); } } public class ExcelDocumentFactory : IDocumentFactory { public IDocument Create() { return new ExcelDocument(); } } public class ExcelXDocumentFactory : IDocumentFactory { public IDocument Create() { return new ExcelXDocument(); } } public class PowerPointDocumentFactory : IDocumentFactory { public IDocument Create() { return new PowerPointDocument(); } } public class PowerPointXDocumentFactory : IDocumentFactory { public IDocument Create() { return new PowerPointXDocument(); } }
客戶端調用代碼:
static void Main(string[] args) { IDocument document; IDocumentFactory documentFactory; documentFactory = new WordDocumentFactory(); document = documentFactory.Create(); document.Import(); documentFactory = new WordXDocumentFactory(); document = documentFactory.Create(); document.Import(); documentFactory = new ExcelDocumentFactory(); document = documentFactory.Create(); document.Import(); documentFactory = new ExcelXDocumentFactory(); document = documentFactory.Create(); document.Import(); documentFactory = new PowerPointDocumentFactory(); document = documentFactory.Create(); document.Import(); documentFactory = new PowerPointXDocumentFactory(); document = documentFactory.Create(); document.Import(); Console.ReadKey(); }
輸出結果:
一個產品一個實現類,一個工廠類,這樣職責單一符合SRP,但是系統中的類在成倍的增加,有點複雜了,如果在增加一個系列的產品那還了得。
那麼能不能減少一些類呢?經過分析我們發現,這些導入的文檔中2007之前的一系列文檔的解析規則基本類似實現的技術也是類似的,2007及以後的文檔的解析規則類似。所以我們可以把這些產品分成兩個系列,2007之前的成為Document系列,2007以後的文檔成為DocumentX系列, 那麼我們就可以創建兩個具體的工廠來創建Document系列和DocumentX系列, 從另一個維度來看,Word 和WordX, Excel 和ExcelX,PowerPoint 和 PowerPointX的的關係也很密切,因為都是同一個產品,只是處在不同的系列上,他們各自的編碼又各自類似,因此在這個維度上可以將其提出一組新的介面,IWordDocument 用於處理word的導入(Word和WordX),IExcelDocument 用處理Excel的導入(Excel 和ExcelX),IPowerPoint用於處理 PowerPoint 導入(PowerPoint和PowerPointX),根據這個思路重構代碼:
UML 圖
代碼:
public interface IWordDocument { void Import(); } public interface IExcelDocument { void Import(); } public interface IPowerPointDocument { void Import(); } public interface IDocumentFactory { IWordDocument CreateWord(); IExcelDocument CreateExcel(); IPowerPointDocument CreatePowerPoint(); } public class WordDocument : IWordDocument { public void Import() { Console.WriteLine("Import Word"); } } public class WordXDocument : IWordDocument { public void Import() { Console.WriteLine("Import WordX"); } } public class ExcelDocument : IExcelDocument { public void Import() { Console.WriteLine("Import Excel"); } } public class ExcelXDocument : IExcelDocument { public void Import() { Console.WriteLine("Import ExcelX"); } } public class PowerPointDocument : IPowerPointDocument { public void Import() { Console.WriteLine("Import Power Point"); } } public class PowerPointXDocument : IPowerPointDocument { public void Import() { Console.WriteLine("Import Power PointX"); } } public class DocumentFactory : IDocumentFactory { public IWordDocument CreateWord() { return new WordDocument(); } public IExcelDocument CreateExcel() { return new ExcelDocument(); } public IPowerPointDocument CreatePowerPoint() { return new PowerPointDocument(); } } public class DocumentXFactory : IDocumentFactory { public IWordDocument CreateWord() { return new WordXDocument(); } public IExcelDocument CreateExcel() { return new ExcelXDocument(); } public IPowerPointDocument CreatePowerPoint() { return new PowerPointXDocument(); } }
客戶端調用:
static void Main(string[] args) { IWordDocument wordDocument; IExcelDocument excelDocument; IPowerPointDocument powerPointDocument; IDocumentFactory documentFactory; documentFactory = new DocumentFactory(); wordDocument = documentFactory.CreateWord(); excelDocument = documentFactory.CreateExcel(); powerPointDocument = documentFactory.CreatePowerPoint(); wordDocument.Import(); excelDocument.Import(); powerPointDocument.Import(); documentFactory = new DocumentXFactory(); wordDocument = documentFactory.CreateWord(); excelDocument = documentFactory.CreateExcel(); powerPointDocument = documentFactory.CreatePowerPoint(); wordDocument.Import(); excelDocument.Import(); powerPointDocument.Import(); Console.ReadKey(); }
輸出:
這一次工廠的數量得到了控制,這裡只有兩個工廠類就搞定了,DocumentFactory 負責創建office 2007之前的文檔對象,DocumentXFactory 負責創建 office 2007以及以後的文檔對象。這就解決了工廠方法模式工廠類隨著產品增加隨之增加帶來的複雜性,以及不易維護的問題。這樣如果在增加一系列產品就變得容易了很多,只需要再創建一個系列產品的具體工廠並繼承自抽象工廠,以及實現系列產品的抽象介面的具體類就可以了。
隨著需求的變化一步一步的經過重構代碼也一步一步從簡單工廠模式到工廠方法模式再到抽象工廠模式了。現在文檔處理代碼就是一個典型的抽象工廠模式了,那麼下麵來看看抽象工廠模式到底是什麼?
二、抽象工廠模式定義:
抽象工廠模式(Abstract Factory Pattern):提供一個創建一系列相關或相互依賴對象的介面,而無須指定它們具體的類。抽象工廠模式又稱為Kit模式,它是一種對象創建型模式。在抽象工廠模式中,每一個具體工廠都提供了多個工廠方法用於產生多種不同類型的產品,這些產品構成了一個產品族(產品系列).
三、工廠方法模式結構圖:
1、AbsctactFactory(抽象抽象工廠):
它聲明瞭一組用於創建一系列產品的方法,每一個方法對應創建一種產品。
2.ConcreteFactory(具體工廠):
它實現了在抽象工廠中聲明的創建產品的方法,生成一組具體產品,這些產品構成了一個產品系列,每個產品都在同一個系列中。
3. AbsctactProdcut(抽象產品):
它為每種產品聲明介面,在抽象產品中聲明瞭產品所具有的業務方法。
4.ConcreteProdcut(具體產品):
它定義具體工廠生產的具體產品對象,實現抽象產品介面中聲明的業務方法。
四、抽象工廠模式代碼實現
在抽象工廠中聲明瞭多個工廠方法,用於創建不同類型的產品,抽象工廠可以是介面,也可以是抽象類或者具體類,其典型代碼如下:
public interface IAbstractProdcutA { void DoSomething(); } public interface IAbstractProdcutB { void DoSomething(); } public interface IAbstractFactory{ IAbstractProdcutA CreateProductA(); IAbstractProdcutB CreateProductB(); } public class ConcreteProductA1:IAbstractProdcutA { public void DoSomething() { Console.WriteLine("I'm ConcreteProductA1"); } } public class ConcreteProductA2:IAbstractProdcutA { public void DoSomething() { Console.WriteLine("I'm ConcreteProductA2"); } } public class ConcreteProductB1:IAbstractProdcutB { public void DoSomething() { Console.WriteLine("I'm ConcreteProductB1"); } } public class ConcreteProductB2:IAbstractProdcutB { public void DoSomething() { Console.WriteLine("I'm ConcreteProductB2"); } } public class ConcreteFactory1 : IAbstractFactory { public IAbstractProdcutA CreateProductA() { return new ConcreteProductA1(); } public IAbstractProdcutB CreateProductB() { return new ConcreteProductB1(); } } public class ConcreteFactory2 : IAbstractFactory { public IAbstractProdcutA CreateProductA() { return new ConcreteProductA2(); } public IAbstractProdcutB CreateProductB() { return new ConcreteProductB2(); } }
客戶端調用代碼:
static void Main(string[] args) { IAbstractProdcutA productA; IAbstractProdcutB productB; IAbstractFactory factory = new ConcreteFactory1(); productA = factory.CreateProductA(); productB = factory.CreateProductB(); productA.DoSomething(); productB.DoSomething(); factory = new ConcreteFactory2(); productA = factory.CreateProductA(); productB = factory.CreateProductB() ; productA.DoSomething(); productB.DoSomething(); Console.ReadKey(); }
輸出:
五 、抽象工廠模式的優點
-
抽象工廠模式隔離了具體類的生成,使得客戶並不需要知道什麼被創建。由於這種隔離,更換一個具體工廠就變得相對容易,所有的具體工廠都實現了抽象工廠中定義的那些公共介面,因此只需改變具體工廠的實例,就可以在某種程度上改變整個軟體系統的行為。比如常用的配置+反射就可輕鬆替換掉工廠進而替換掉整個以為邏輯。
-
當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品系列中的對象。
-
增加新的產品系列很方便,無須修改已有系統,只需要增加具體產品類和具體工廠就可以了符合“開閉原則(OCP)“。
六、抽象工廠模式的缺點
- 增加新的產品麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層代碼,這顯然會帶來較大的不便,違背了“開閉原則(OCP)“。如果新增一個產品就要在抽象工廠中增加一個創建該產品的方法。這樣就牽一發動全身,原來的所有集成該抽象工廠的具體工廠都要修改。
七、抽象工廠模式的使用場景
- 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是很重要的,用戶無須關心對象的創建過程,將對象的創建和使用解耦。
- 系統中有多於一個的產品系列,而每次只使用其中某一產品系列。可以通過配置文件等方式來使得用戶可以動態改變產品系列,也可以很方便地增加新的產品系列。
- 屬於同一個產品系列的產品將在一起使用,這一約束必須在系統的設計中體現出來。同一個產品系列中的產品可以是沒有任何關係的對象,但是它們都具有一些共同的約束,如Office 文檔的支持,在window xp 重視不能使用Office 2007 +的,window 7 支持0ffice 2007,因此這個約束就是操作系統對office版本的支持。
- 產品類型穩定,設計完成之後,不會向系統中增加新的產品類型或者刪除已有的產品產品類型。
八、擴展
第三次需求改變:從商業化上考慮甲方要求這個軟體的某些版本只提供導入office 2007 之前的文件,某些版本只支持2007以後版本。
怎麼辦呢? 這個需求開發人員拿到後一定竊喜,抽象工廠實現這種需求簡直是太容易了。配置+反射呀, 將具體工廠配置在配置文件中,代碼中只是用抽象並通過反射來創建工廠實進而達到動態控制的能力。
如果軟體版本中V1中只要支持office 2007以前的文檔版本。那麼在配置文件App.config 增加以下節點:
配置完節點後在代碼中通過反射來創建工廠類, 代碼如下
static void Main(string[] args) { IWordDocument wordDocument; IExcelDocument excelDocument; IPowerPointDocument powerPointDocument; IDocumentFactory documentFactory; // 讀取配置文件並且通過反射創建工廠實例 var setting = ConfigurationSettings.AppSettings["DocumentFaccory"]; var obj = Type.GetType(setting); if (obj == null) return; documentFactory = Activator.CreateInstance(obj) as IDocumentFactory; if (documentFactory == null) return; wordDocument = documentFactory.CreateWord(); excelDocument = documentFactory.CreateExcel(); powerPointDocument = documentFactory.CreatePowerPoint(); wordDocument.Import(); excelDocument.Import(); powerPointDocument.Import(); Console.ReadKey(); }
輸出結果如下:
如果在V1.1版本中甲方提出要求只支持office 2007以後的版本,那麼只需要修改配置文件如下就可以了:
把原來的
<appSettings> <add key="DocumentFaccory" value="DesignPattern.AbstractFactory.DocumentFactory"/> </appSettings>
改成:
<appSettings> <add key="DocumentFaccory" value="DesignPattern.AbstractFactory.DocumentXFactory"/> </appSettings>
其它地方不用做任何修改,輸出結果: