工廠模式之抽象工廠模式
圖顯示有問題。可直接看:
點擊打開鏈接
場景問題
舉個生活中常見的樣例——組裝電腦,我們在組裝電腦的時候。通常須要選擇一系列的配件,比方CPU、硬盤、內存、主板、電源、機箱等。
為討論使用簡單點,僅僅考慮選擇CPU和主板的問題。
其實。在選擇CPU的時候。面臨一系列的問題,比方品牌、型號、針腳數目、主頻等問題。僅僅有把這些問題都確定下來,才幹確定詳細的CPU。
相同,在選擇主板的時候。也有一系列問題。比方品牌、芯片組、集成芯片、總線頻率等問題,也僅僅有這些都確定了,才幹確定詳細的主板。
選擇不同的CPU和主板,是每一個客戶在組裝電腦的時候,向裝機公司提出的要求,也就是我們每一個人自己擬定的裝機方案。
在終於確定這個裝機方案之前。還須要總體考慮各個配件之間的兼容性。比方:CPU和主板,假設使用Intel的CPU和AMD的主板是根本無法組裝的。
由於Intel的CPU針腳數與AMD主板提供的CPU插口不兼容。就是說假設使用Intel的CPU根本就插不到AMD的主板中,所以裝機方案是總體性的。裏面選擇的各個配件之間是有關聯的。
對於裝機project師而言。他僅僅知道組裝一臺電腦,須要對應的配件,可是詳細使用什麽樣的配件,還得由客戶說了算。也就是說裝機project師僅僅是負責組裝,而客戶負責選擇裝配所須要的詳細的配件。因此,當裝機project師為不同的客戶組裝電腦時,僅僅須要依據客戶的裝機方案,去獲取對應的配件,然後組裝就可以。
使用簡單工廠模式的解決方式
考慮客戶的功能。須要選擇自己須要的CPU和主板。然後告訴裝機project師自己的選擇,接下來就等著裝機project師組裝電腦了。
對裝機project師而言,僅僅是知道CPU和主板的接口,而不知道詳細實現,非常明顯能夠用上簡單工廠模式或工廠方法模式。為了簡單,這裏選用簡單工廠。
客戶告訴裝機project師自己的選擇。然後裝機project師會通過對應的工廠去獲取對應的實例對象。
源碼
CPU接口與詳細實現
publicinterface Cpu { publicvoid calculate(); }
publicclass IntelCpu implementsCpu { /** * CPU的針腳數 */ privateint pins = 0; public IntelCpu(int pins){ this.pins = pins; } @Override publicvoid calculate() { System.out.println("Intel CPU的針腳數:" + pins); } }
publicclass AmdCpu implements Cpu { privateint pins = 0; public AmdCpu(int pins){ this.pins = pins; } @Override publicvoid calculate() { System.out.println("AMD CPU的針腳數:" + pins); } }
主板接口與詳細實現
publicinterface Mainboard { publicvoid installCPU(); }
publicclass IntelMainboard implements Mainboard { /** * CPU插槽的孔數 */ privateint cpuHoles = 0; // 構造方法,傳入CPU插槽的孔數 public IntelMainboard(int cpuHoles){ this.cpuHoles = cpuHoles; } @Override publicvoid installCPU() { System.out.println("Intel主板的CPU插槽孔數是:" + cpuHoles); } }
publicclass AmdMainboard implements Mainboard { privateint cpuHoles = 0; //構造方法,傳入CPU插槽的孔數 public AmdMainboard(int cpuHoles){ this.cpuHoles = cpuHoles; } @Override publicvoid installCPU() { System.out.println("AMD主板的CPU插槽孔數是:" + cpuHoles); } }
CPU與主板工廠類
publicclass CpuFactory { publicstatic Cpu createCpu(int type){ Cpu cpu = null; if(type == 1){ cpu = new IntelCpu(755); }elseif(type == 2){ cpu = new AmdCpu(938); } return cpu; } }
publicclass MainboardFactory { publicstatic Mainboard createMainboard(int type){ Mainboard mainboard = null; if(type == 1){ mainboard = new IntelMainboard(755); }elseif(type == 2){ mainboard = new AmdMainboard(938); } return mainboard; } }
裝機project師類與客戶類例如以下:
publicclass ComputerEngineer { //定義組裝機須要的CPU private Cpu cpu = null; //定義組裝機須要的主板 private Mainboard mainboard = null; publicvoid makeComputer(int cpuType , int mainboard){ /** * 組裝機器的基本步驟 */ //1:首先準備好裝機所須要的配件 prepareHardwares(cpuType, mainboard); //2:組裝機器 //3:測試機器 //4:交付客戶 } privatevoid prepareHardwares(int cpuType , int mainboard){ //這裏要去準備CPU和主板的詳細實現。為了演示樣例簡單。這裏僅僅準備這兩個 //但是,裝機project師並不知道怎樣去創建,怎麽辦呢? //直接找對應的工廠獲取 this.cpu = CpuFactory.createCpu(cpuType); this.mainboard = MainboardFactory.createMainboard(mainboard); //測試配件是否好用 this.cpu.calculate(); this.mainboard.installCPU(); } }
publicclass Client { publicstaticvoid main(String[]args){ ComputerEngineer cf = new ComputerEngineer(); cf.makeComputer(1,1); } }
執行結果例如以下: Intel CPU的針腳數:755 Intel主板的CPU插槽孔數是:755 上面的實現,盡管通過簡單工廠方法攻克了:對於裝機project師,僅僅知CPU和主板的接口,而不知道詳細實現的問題。但另一個問題沒有解決,那就是這些CPU對象和主板對象事實上是有關系的,須要相互匹配的。而上面的實現中,並沒有維護這樣的關聯關系,CPU和主板是由客戶隨意選擇。這是有問題的。
比方在client調用makeComputer時,傳入參數為(1,2),執行結果如下:
Intel CPU的針腳數:755 AMD主板的CPU插槽孔數是:938 觀察上面結果就會看出問題。客戶選擇的是Intel的CPU針腳數為755。而選擇的主板是AMD,主板上的CPU插孔是938,根本無法組裝。這就是沒有維護配件之間的關系造成的。該怎麽解決問題呢?
引進抽象工廠模式
每個模式都是針對一定問題的解決方式。抽象工廠模式與工廠方法模式的最大差別就在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則須要面對多個產品等級結構。
在學習抽象工廠詳細實例之前,應該明確兩個重要的概念:產品族和產品等級
所謂產品族。是指位於不同產品等級結構中,功能相關聯的產品組成的家族。比方AMD的主板、芯片組、CPU組成一個家族,Intel的主板、芯片組、CPU組成一個家族。而這兩個家族都來自於三個產品等級:主板、芯片組、CPU。一個等級結構是由同樣的結構的產品組成,示意圖例如以下:
顯然。每個產品族中含有產品的數目,與產品等級結構的數目是相等的。產品的等級結構與產品族將產品依照不同方向劃分,形成一個二維的坐標系。
橫軸表示產品的等級結構,縱軸表示產品族,上圖共同擁有兩個產品族,分布於三個不同的產品等級結構中。僅僅要指明一個產品所處的產品族以及它所屬的等級結構,就能夠唯一的確定這個產品。
上面所給出的三個不同的等級結構具有平行的結構。因此。假設採用工廠方法模式,就勢必要使用三個獨立的工廠等級結構來對付這三個產品等級結構。
因為這三個產品等級結構的相似性,會導致三個平行的工廠等級結構。隨著產品等級結構的數目的添加,工廠方法模式所給出的工廠等級結構的數目也會隨之添加。例如以下圖:
那麽,能否夠使用同一個工廠等級結構來對付這些同樣或者極為相似的產品等級結構呢?當然能夠的。並且這就是抽象工廠模式的優點。同一個工廠等級結構負責三個不同產品等級結構中的產品對象的創建。
能夠看出。一個工廠等級結構能夠創建出分屬於不同產品等級結構的一個產品族中的全部對象。顯然,這時候抽象工廠模式比簡單工廠模式、工廠方法模式更有效率。相應於每個產品族都有一個詳細工廠。
而每個詳細工廠負責創建屬於同一個產品族,可是分屬於不同等級結構的產品。
抽象工廠模式結構
抽象工廠模式是對象的創建模式。它是工廠方法模式的進一步推廣。
如果一個子系統須要一些產品對象,而這些產品又屬於一個以上的產品等級結構。
那麽為了將消費這些產品對象的責任和創建這些產品對象的責任切割開來,能夠引進抽象工廠模式。這種話。消費產品的一方不須要直接參與產品的創建工作,而僅僅須要向一個公用的工廠接口請求所須要的產品。
通過使用抽象工廠模式。能夠處理具有同樣(或者相似)等級結構中的多個產品族中的產品對象的創建問題。
例如以下圖所看到的:
因為這兩個產品族的等級結構同樣。因此使用同一個工廠族也能夠處理這兩個產品族的創建問題。這就是抽象工廠模式。
依據產品角色的結構圖。就不難給出工廠角色的結構設計圖。
能夠看出。每個工廠角色都有兩個工廠方法,分別負責創建分屬不同產品等級結構的產品對象。下圖ComputerEngineer依賴於AbstractFactory、Cpu和MainBoard三個接口。
源碼
前面演示樣例中CPU接口和CPU實現類,主板接口和主板實現類。都不須要變化。
前面演示樣例中創建CPU的工廠類和創建主板的工廠類,都不再須要。
新加的抽象工廠類和實現類:
publicinterface AbstractFactory { /** * 創建CPU對象 */ public Cpu createCpu(); /** * 創建主板對象 */ public Mainboard createMainboard(); }
publicclass IntelFactory implements AbstractFactory { @Override public Cpu createCpu() { returnnew IntelCpu(755); } @Override public Mainboard createMainboard() { returnnew IntelMainboard(755); } }
publicclass AmdFactory implements AbstractFactory { @Override public Cpu createCpu() { returnnew IntelCpu(938); } @Override public Mainboard createMainboard() { returnnew IntelMainboard(938); } }
裝機project師類跟前面的實現相比,基本的變化是:從client不再傳入選擇CPU和主板的參數,而是直接傳入客戶已經選擇好的產品對象。
這樣就避免了單獨去選擇CPU和主板所帶來的兼容性問題,客戶要選就是一套。就是一個系列。
publicclass ComputerEngineer { private Cpu cpu = null; private Mainboard mainboard = null; publicvoid makeComputer(AbstractFactory af){ prepareHardwares(af); } privatevoid prepareHardwares(AbstractFactory af){ this.cpu = af.createCpu(); this.mainboard = af.createMainboard(); this.cpu.calculate(); this.mainboard.installCPU(); } }
client代碼:
publicclass Client { publicstaticvoid main(String[]args){ //創建裝機project師對象 ComputerEngineer cf = new ComputerEngineer(); //客戶選擇並創建須要使用的產品對象 AbstractFactory af = new IntelFactory(); //告訴裝機project師自己選擇的產品,讓裝機project師組裝電腦 cf.makeComputer(af); } }
抽象工廠的功能是為一系列相關對象或相互依賴的對象創建一個接口。一定要註意,這個接口內的方法不是隨意堆砌的。而是一系列相關或相互依賴的方法。
比方上面樣例中的主板和CPU。都是為了組裝一臺電腦的相關對象。不同的裝機方案,代表一種詳細的電腦系列。
因為抽象工廠定義的一系列對象一般是相關或相互依賴的,這些產品對象就構成了一個產品族。也就是抽象工廠定義了一個產品族。
這就帶來很大的靈活性。切換產品族的時候。僅僅要提供不同的抽象工廠實現就能夠了。也就是說如今是以一個產品族作為一個總體被切換。
在什麽情況下應當使用抽象工廠模式
1.一個系統不應當依賴於產品類實例怎樣被創建、組合和表達的細節,這對於全部形態的工廠模式都是重要的。
2.這個系統的產品有多於一個的產品族,而系統僅僅消費當中某一族的產品。
3.同屬於同一個產品族的產品是在一起使用的,這一約束必須在系統的設計中體現出來。(比方:Intel主板必須使用Intel CPU、Intel芯片組)
4.系統提供一個產品類的庫。全部的產品以相同的接口出現,從而使client不依賴於實現。
抽象工廠模式的起源
抽象工廠模式的起源或者最早的應用,是用於創建分屬於不同操作系統的視窗構建。
比方:命令按鍵(Button)與文字框(Text)都是視窗構建,在UNIX操作系統的視窗環境和Windows操作系統的視窗環境中,這兩個構建有不同的本地實現,它們的細節有所不同。
在每個操作系統中,都有一個視窗構建組成的構建家族。在這裏就是Button和Text組成的產品族。而每個視窗構件都構成自己的等級結構,由一個抽象角色給出抽象的功能描寫敘述,而由詳細子類給出不同操作系統下的詳細實現。
能夠發如今上面的產品類圖中,有兩個產品的等級結構,各自是Button等級結構和Text等級結構。同一時候有兩個產品族。也就是UNIX產品族和Windows產品族。
UNIX產品族由UNIX Button和UNIX Text產品構成;而Windows產品族由Windows Button和Windows Text產品構成。
系統對產品對象的創建需求由一個project的等級結構滿足。當中有兩個詳細project角色,即UnixFactory和WindowsFactory。UnixFactory對象負責創建Unix產品族中的產品,而WindowsFactory對象負責創建Windows產品族中的產品。這就是抽象工廠模式的應用,抽象工廠模式的解決方式例如以下圖:
顯然,一個系統僅僅可以在某一個操作系統的視窗環境下執行,而不能同一時候在不同的操作系統上執行。
所以。系統實際上僅僅能消費屬於同一個產品族的產品。
在現代的應用中,抽象工廠模式的使用範圍已經大大擴大了,不再要求系統僅僅能消費某一個產品族了。因此。能夠不必理會前面所提到的原始用意。
抽象工廠模式的長處
- 分離接口和實現
client使用抽象工廠來創建須要的對象,而client根本就不知道詳細的實現是誰,client僅僅是面向產品的接口編程而已。也就是說。client從詳細的產品實現中解耦。
- 使切換產品族變得easy
由於一個詳細的工廠實現代表的是一個產品族,比方上面樣例的從Intel系列到AMD系列僅僅須要切換一下詳細工廠。
- 添加產品族變的easy
假設出現新的產品族,僅僅需新增抽象工廠的一個實現類用於處理新產品族,其他實現類都不需改變、
抽象工廠模式的缺點
- 不太easy擴展新的產品(不是產品族,而是產品等級,如顯卡)
假設須要給整個產品族加入一個新的產品,那麽就須要改動抽象工廠,這樣就會導致改動全部的工廠實現類。
工廠模式之抽象工廠模式