簡單工廠模式是一個工廠類根據工廠方法的參數建立不出不一樣的產品, 工廠方法模式是每個產品都有一個一一對應的工廠負責建立該產品。那麼今天要講的抽象工廠模式是一個工廠可以產生關聯的一系列產品。抽象工廠模式相對於簡單工廠和工廠方法模式來着更具抽象性。html
咱們先來看一個簡單的需求: 甲方要開發一套辦公自動化軟件,其中有一個很是重要的功能就是要可以導入Word 文檔和Excel 文檔。編程
開發人員拿到需求後就開始編碼了, 很快代碼寫完了:設計模式
public class ImportTool { public void ImportWord() { Console.WriteLine("Import Word"); } public void ImportExcel() { Console.WriteLine("Import Excel"); } }
客戶端調用代碼:app
class Program { static void Main(string[] args) { ImportTool importTool = new ImportTool(); importTool.ImportWord(); importTool.ImportExcel(); Console.ReadKey(); } }
輸出結果:編碼
看起來不錯, 可是看代碼,客戶端的代碼和具體的實現之間是直接new出來的 ,簡直就是面向具體編程,沒有接口沒有抽象,是否是違背了ISP原則了?,那好開發人員決定提出一個抽象層,提出一個抽象的文檔接口, 用簡單工廠模式來實現這個需求:spa
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; } }
客戶端調用:excel
static void Main(string[] args) { IDocument document=DocumentFactory.Create("word"); document.Import(); document=DocumentFactory.Create("excel"); document.Import(); Console.ReadKey(); }
輸出:code
看起來完美這是一個標準的靜態工廠模式的實現。
第一次需求變動: 增長 對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模式,它是一種對象建立型模式。在抽象工廠模式中,每個具體工廠都提供了多個工廠方法用於產生多種不一樣類型的產品,這些產品構成了一個產品族(產品系列).
它聲明瞭一組用於建立一系列產品的方法,每個方法對應建立一種產品。
它實現了在抽象工廠中聲明的建立產品的方法,生成一組具體產品,這些產品構成了一個產品系列,每一個產品都在同一個系列中。
它爲每種產品聲明接口,在抽象產品中聲明瞭產品所具備的業務方法。
它定義具體工廠生產的具體產品對象,實現抽象產品接口中聲明的業務方法。
在抽象工廠中聲明瞭多個工廠方法,用於建立不一樣類型的產品,抽象工廠能夠是接口,也能夠是抽象類或者具體類,其典型代碼以下:
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)「。
第三次需求改變:從商業化上考慮甲方要求這個軟件的某些版本只提供導入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>
其它地方不用作任何修改,輸出結果: