上一篇的工廠方法模式引入了工廠等級結構,解決了在原來簡單工廠模式中工廠類職責過重的原則,可是因爲工廠方法模式的每一個工廠只生產一類產品,可能會致使系統中存在大量的工廠類,從而增長系統開銷。那麼,咱們應該怎麼來重構?彷佛,咱們能夠考慮將一些相關的產品組成一個「產品族」,由同一個工廠來統一輩子產,這就是本次將要學習的抽象工廠模式的基本思想。編程
抽象工廠模式(Abstract Factory) | 學習難度:★★★★☆ | 使用頻率:★★★★★ |
M公司IT開發部接到一個開發任務,想要對之前的一個系統開發一套界面皮膚庫,能夠對該桌面系統軟件進行界面美化。這樣,用戶就能夠在使用時經過菜單來選擇皮膚,不一樣的皮膚將提供視覺效果不一樣的按鈕、文本框以及組合框等界面元素,其結構示意圖以下所示:設計模式
該皮膚庫須要具有良好的靈活性和可擴展性,用戶能夠自由選擇不一樣的皮膚,開發人員也能夠在不修改既有代碼的基礎上增長新的皮膚。app
M公司的開發人員針對上述需求,決定現學現賣,在上次使用了工廠方法模式以後對工廠方法模式大爲讚揚,決定使用工廠方法模式來進行系統的設計,爲了保證系統的靈活性和可擴展性,提供一系列具體工廠來建立按鈕、文本框以及組合框等界面元素,客戶端針對抽象工廠來編程,初始結構以下圖所示:ide
從上圖能夠看出,此方案提供了大量的工廠來建立具體的界面組件,能夠經過配置文件來更換具體界面組件從而改變界面的風格,可是,此方案存在如下問題:學習
(1)須要增長新的皮膚時,雖然不須要更改現有代碼,可是須要增長大量的類,針對每個新增具體組件都要增長一個具體工廠,類的個數會成對增長。這無疑會致使系統愈來愈龐大,從而增長了系統的維護成本和運行開銷。測試
(2)因爲同一種風格的不一樣界面組件一般須要一塊兒顯示,所以須要爲每一個組件都選擇一個具體工廠,用戶在使用時必須逐個進行設置,若是某個具體工廠選擇失誤將會致使界面顯示混亂,雖然能夠適當增長一些約束語句,可是客戶端代碼和配置文件都較爲複雜。spa
綜上所述,如何減小系統中類的個數並保證客戶端每次始終就只使用一種風格的具體界面組件?這是縈繞在M公司開發人員心頭的兩個問題。設計
(1)產品等級3d
產品等級即產品的繼承結構,例如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構,抽象電視機是父類,而具體品牌電視機則是子類。調試
(2)產品族
產品族是指由同一個工廠生產的,位於不一樣產品等級結構中的一組,例如海爾電器廠生產的海爾電視機、海爾電冰箱,海爾電視機位於電視機產品等級結構中,而海爾電冰箱則位於電冰箱產品等級結構中,他們倆構成了一個產品族。
產品等級與產品族的示意圖以下圖所示:
能夠看出,當系統所提供的工廠生產的具體產品並非一個簡單的對象,而是多個位於不一樣產品等級結構、屬於不一樣類型的具體產品時,就可使用抽象工廠模式。抽象工廠模式是全部形式的工廠模式中最爲抽象和最具通常性的一種形式。
Note :抽象工廠與工廠方法最大的區別就在於,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式須要面對多個產品等級結構,一個工廠等級結構能夠負責多個不一樣產品等級中的產品對象的建立。
抽象工廠模式爲建立一組對象提供了一種方案,與工廠方法模式相比,抽象工廠模式中的具體工廠不僅是建立一種產品,它負責建立一族產品。其定義以下:
Definition :抽象工廠模式提供一個建立一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠模式又稱爲Kit模式,它是一種對象建立型模式。
抽象工廠模式的結構圖以下圖所示:
(1)Abstract Factory (抽象工廠角色):聲明瞭一組用於建立一族產品的方法,每個方法對應一種產品。
(2)Concrete Factory (具體工廠角色):實現了在抽象工廠中聲明的建立產品的方法,生成一組具體產品,這些產品構成了一個產品族。
(3)Abstract Product (抽象產品角色):爲每種產品聲明接口,在抽象產品中聲明瞭全部的業務方法。
(4)Concrete Product (具體產品角色):定義具體工廠生產的具體產品對象,實如今抽象產品接口中聲明的業務方法。
M公司使用抽象工廠模式來重構了界面皮膚庫的設計,其基本結構以下圖所示:
(1)Abstract Product
public interface IButton { void Display(); } public interface ITextField { void Display(); } public interface IComboBox { void Display(); }
(2)Concrete Product
① Spring風格Button
public class SpringButton : IButton { public void Display() { Console.WriteLine("顯示淺綠色按鈕..."); } } public class SpringTextField : ITextField { public void Display() { Console.WriteLine("顯示綠色邊框文本框..."); } } public class SpringComboBox : IComboBox { public void Display() { Console.WriteLine("顯示綠色邊框下拉框..."); } }
② Summer風格Button
public class SummerButton : IButton { public void Display() { Console.WriteLine("顯示淺藍色按鈕..."); } } public class SummerTextField : ITextField { public void Display() { Console.WriteLine("顯示藍色邊框文本框..."); } } public class SummerComboBox : IComboBox { public void Display() { Console.WriteLine("顯示藍色邊框下拉框..."); } }
(3)Abstract Factory
public interface ISkinFactory { IButton CreateButton(); ITextField CreateTextField(); IComboBox CreateComboBox(); }
(4)Concrete Factory
① Spring皮膚工廠
// Spring皮膚工廠 public class SpringSkinFactory : ISkinFactory { public IButton CreateButton() { return new SpringButton(); } public IComboBox CreateComboBox() { return new SpringComboBox(); } public ITextField CreateTextField() { return new SpringTextField(); } }
② Summer皮膚工廠
public class SummerSkinFactory : ISkinFactory { public IButton CreateButton() { return new SummerButton(); } public IComboBox CreateComboBox() { return new SummerComboBox(); } public ITextField CreateTextField() { return new SummerTextField(); } }
(1)客戶端代碼
public class Program { public static void Main(string[] args) { ISkinFactory skinFactory = (ISkinFactory) AppConfigHelper.GetSkinFactoryInstance(); if (skinFactory == null) { Console.WriteLine("讀取當前選中皮膚類型失敗..."); } IButton button = skinFactory.CreateButton(); ITextField textField = skinFactory.CreateTextField(); IComboBox comboBox = skinFactory.CreateComboBox(); button.Display(); textField.Display(); comboBox.Display(); Console.ReadKey(); } }
其中,AppConfigHelper用於從如下的配置文件中讀取SkinFactory的值去反射生成具體工廠實例,這裏配置的當前選中皮膚是Spring風格。
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <!-- 當前選中皮膚類型 --> <add key="SkinFactory" value="Manulife.ChengDu.DesignPattern.AbstractFactory.SpringSkinFactory, Manulife.ChengDu.DesignPattern.AbstractFactory"/> </appSettings> </configuration>
AppConfigHelper的具體代碼以下:
public class AppConfigHelper { public static string GetSkinFactoryName() { string factoryName = null; try { factoryName = System.Configuration.ConfigurationManager.AppSettings["SkinFactory"]; } catch (Exception ex) { Console.WriteLine(ex.Message); } return factoryName; } public static object GetSkinFactoryInstance() { string assemblyName = AppConfigHelper.GetSkinFactoryName(); Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type); return instance; } }
(2)調試結果
終於到了興奮的時刻啦,運行結果以下:
這時,咱們將配置文件中的值改成SummerSkinFactory試試:
<add key="SkinFactory" value="Manulife.ChengDu.DesignPattern.AbstractFactory.SummerSkinFactory, Manulife.ChengDu.DesignPattern.AbstractFactory"/>
再次運行程序,結果是咱們想要的:
(1)隔離了具體類的生成,使得客戶並不須要知道什麼被建立。由於這種隔離,所以更換一個具體工廠就變得相對容易。
(2)當一個產品族中的多個對象被設計稱一塊兒工做時,它可以保證客戶端始終只使用同一個產品族中的對象。
(3)增長新的產品族很方便,無需修改已有系統,符合開閉原則。
增長新的產品等級結構很麻煩,增長新的產品等級結構很麻煩,增長新的產品等級結構很麻煩!!!(重要的事情說三遍)由於須要對原有系統進行較大的修改,甚至須要修改抽象層代碼,這必然會帶來較大的不便,在這個角度,它違背了開閉(對擴展開放,對修改封閉)原則。
想一想,若是咱們須要爲單選按鈕(RadioButton)提供不一樣皮膚的風格化顯示,會發現不管選擇哪一種皮膚,單選按鈕都顯得「格格不入」。
(1)用戶無須關心對象的建立過程,須要將對象的建立和使用解耦 -> 這是全部工廠模式的使用前提
(2)系統中有多餘一個的產品族,而每次都只使用其中的某一種產品族。 -> 能夠經過配置文件等方式來使得用戶能夠動態地改變產品族,也能夠很方便地增長新的產品族
(3)產品等級結構穩定!設計完成以後,不會向系統中增長新的產品等級結構或刪除已有產品等級結構。 -> 並不太符合開閉原則
劉偉,《設計模式的藝術—軟件開發人員內功修煉之道》