繼續接 建立多個「產品」的方式——工廠方法模式總結。html
如今又有了新的需求:果廠裏新進了一批進口水果:進口香蕉,進口蘋果,進口梨,一樣的是須要採集水果,以前的程序只是對工廠進行了抽象,使得不一樣的產品對應各自的工廠,而產品僅是國內水果,如今涉及到了進口水果,如今有了兩大類的產品,每一個產品又分爲不一樣的等級,咱們叫它產品族。java
所謂產品族,是指位於不一樣產品等級結構中,功能相關聯的產品組成的家族。apache
一、每個產品族中含有產品的數目與產品等級結構的數目是相等的編程
二、產品的等級結構與產品族將產品按照不一樣方向劃分,造成一個二維的座標系。設計模式
橫軸表示產品的等級結構,縱軸表示產品族。app
三、只要指明一個產品所處的產品族以及它所屬的等級結構,就能夠惟一的肯定這個產品。ide
一、國產水果和進口水果就是產品族(縱座標)post
二、蘋果,香蕉,鴨梨等,就是產品的等級結構,具體每一個產品族的等級ui
這個業務就相對複雜了,因此以前的模式就變得很差用。若是硬要使用,那就是在具體的蘋果工廠類裏再增長一個新的方法——返回進口蘋果類的實例,同時也爲進口蘋果增長對應的進口蘋果類,同理對於香蕉,鴨梨也是同樣的,好像也沒什麼問題。url
如今需求又變了,果園廠增長了溫室種植技術,有了一種新的產品——溫室種植水果。如何寫代碼?
按照工廠方法模式的寫法,天然就要在具體的蘋果工廠類裏再增長一個新的方法——返回溫室蘋果類的實例,同時也要增長溫室蘋果這個新的產品……其餘水果工廠類是同樣的作法。這明顯違背了 OCP 原則,並且蘋果工廠類既能生成進口蘋果也能生產國產蘋果,違背了單一職責原則。此時抽象工廠模式就派上了用場。
一、抽象工廠模式是全部形態的工廠模式中最爲抽象和通常性的。
二、抽象工廠模式能夠向客戶端提供一個接口,使得客戶端在沒必要指定產品的具體類型的狀況下,可以建立多個產品族的產品對象。
水果廠有進口水果,國產水果兩個產品族,而具體得到哪一個產品族是客戶端調用決定的。好比,進口蘋果,進口橘子,國產蘋果,國產橘子等……蘋果,橘子是產品族(y軸)擁有的產品等級(x軸),客戶端能夠調用某個產品族的某個產品,以前的工廠方法模式,就對應一個產品族的設計模式,只有一個水果工廠去維持各水果實體類(產品等級),只有x軸,沒有y軸,客戶端採集進口蘋果,調用的是原先國產蘋果工廠裏新增長的進口蘋果生產方法……很彆扭
採用抽象工廠模式,就須要維持一個 y 軸,解耦各個產品族,提供一個接口給客戶端,讓客戶端能在不指定具體類型的前提下,建立多個產品族……
代碼以下:
一個水果的接口,維持一個得到水果的規則,全部產品等級對象的父類(接口),它負責描述全部實例所共有的公共接口
public interface Fruit { void get(); }
對應產品等級的結構:蘋果和香蕉組成一個產品族的產品等級,這裏昇華爲水果的抽象類,是具體產品的父類
public abstract class AppleA implements Fruit { // 由於橫向x軸的產品等級,有蘋果,香蕉,可是多了縱向的其餘產品族的蘋果,香蕉,那麼產品的抽象要進一步體現出來,蘋果類變爲 // 抽象基類,分別去維持多個和蘋果相關的產品族對應的產品等級 public abstract void get(); } public abstract class BananaA implements Fruit { public abstract void get(); }
具體的產品
public class ForeignApple extends AppleA { @Override public void get() { System.out.println("進口蘋果"); } } public class ForeignBanana extends BananaA { @Override public void get() { System.out.println("進口香蕉"); } } public class HomeApple extends AppleA { @Override public void get() { System.out.println("國產蘋果"); } } public class HomeBanana extends BananaA { @Override public void get() { System.out.println("國產香蕉"); } }
抽象工廠類——抽象工廠模式的核心,包含對多個產品等級結構的聲明,任何具體工廠類都必須實現這個接口
// 一個抽象的工廠類(這裏是接口)去維持產品族——y軸 public interface FruitFactory { // 每個工廠子類(產品族)都有對應的得到產品等級的方法——x軸 Fruit getApple(); Fruit getBanana(); }
具體工廠類是抽象工廠的一個實現,負責實例化某個產品族中的產品等級的對象
public class ForeignFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new ForeignApple(); } @Override public Fruit getBanana() { return new ForeignBanana(); } } public class HomeFruitFactory implements FruitFactory { @Override public Fruit getApple() { return new HomeApple(); } @Override public Fruit getBanana() { return new HomeBanana(); } }
客戶端調用
public class Main { public static void main(String[] args) { // 得到某一個產品族 FruitFactory fruitFactory = new ForeignFruitFactory(); // 得到該產品族下的產品等級 Fruit apple = fruitFactory.getApple(); apple.get(); Fruit banana = fruitFactory.getBanana(); banana.get(); // 得到國產水果產品族 FruitFactory homeFruitFactory = new HomeFruitFactory(); // 得到該產品族下的產品等級 Fruit apple1 = homeFruitFactory.getApple(); apple1.get(); Fruit banana1 = homeFruitFactory.getBanana(); banana1.get(); } }
當之後引入溫室水果的時候,除了必需要創建溫室蘋果類,溫室香蕉類去繼承對應的水果抽象類以外,只須要再創建一個溫室水果工廠類去實現抽象工廠接口便可,已經存在的代碼不須要修改。
類圖以下
一、抽象工廠模式中的方法對應產品等級結構,具體子工廠對應不一樣的產品族。
二、產品族,簡單理解就是不一樣的產品類型
三、產品等級結構,簡單理解就是一個產品類型裏的具體的產品
優勢
一、分離接口和實現
客戶端使用抽象工廠來建立須要的對象,而客戶端根本就不知道具體的實現是誰,客戶端只是面向產品的接口編程而已。也就是說,客戶端從具體的產品實現中解耦
二、使切換產品族變得容易
因一個具體的工廠實現表明的是一個產品族,切換產品族只須要切換一下具體工廠
缺點
不太容易擴展新的產品,如須要給整個產品族添加一個新的產品,那麼就須要修改抽象工廠(增長新的接口方法),這樣就會致使修改全部的工廠實現類。好比增長橘子這個產品等級……
也就是說,縱向不怕擴展,橫向不方便擴展
抽象工廠模式與工廠方法模式的最大區別就在於,工廠方法模式針對的是一個產品等級結構(蘋果,鴨梨,橘子……)
而抽象工廠模式則須要面對多個產品等級結構(進口、國產、溫室栽培……的蘋果,鴨梨,橘子……)
系統的產品有多於一個的產品族,而系統只消費其中某一族的產品
常見有:DocumentBuilderFactory 使用了抽象工廠模式:使程序可以從 XML 文檔獲取生成 DOM 對象樹的解析器
DOM:Document Object Model 的縮寫,即文檔對象模型。XML將數據組織爲一顆樹,因此 DOM 就是對這顆樹的一個對象描敘。
通俗的說是經過解析 XML 文檔,爲 XML 文檔在邏輯上創建一個樹模型,樹的節點是一個個對象。經過存取這些對象就可以存取 XML 文檔的內容。
咱們來看一個簡單的例子,看看 DocumentBuilderFactory 是如何使用的抽象工廠模式來操做一個 XML 文檔的。這是一個XML文檔:
<?xml version="1.0" encoding="UTF-8"?> <messages> <message>Good-bye serialization, hello Java!</message> </messages>
把這個文檔的內容解析到 Java 對象,供程序使用。
首先須要 DocumentBuilderFactory 創建一個解析器工廠,利用這個工廠來得到一個具體的解析器對象,獲取 DocumentBuilderFactory
的新實例。用下面這個 static 方法建立一個新的工廠實例
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
newInstance 方法源碼以下
public static DocumentBuilderFactory newInstance() { return FactoryFinder.find( /* The default property name according to the JAXP spec */ DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory" /* The fallback implementation class name */ "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); }
在這裏使用 DocumentBuilderFacotry 抽象類的目的是爲了建立與具體解析器無關的程序,當 DocumentBuilderFactory 類的靜態方法 newInstance() 被調用時,它根據一個系統變量來決定具體使用哪個解析器。
當得到一個 DocumentBuilderFactory 工廠對象後,使用它的靜態方法 newDocumentBuilder() ,能夠得到一個 DocumentBuilder 對象。
DocumentBuilder db = dbf.newDocumentBuilder();
DocumentBuilder,也就是 db 這個對象,表明了具體的 DOM 解析器(具體的某個產品族),但具體是哪種解析器,好比微軟的或者IBM的,對於程序而言並不重要。
獲取此類實例以後,將能夠利用這個解析器來對XML文檔進行解析:
Document doc = db.parse("xxx.xml");
這個解析器能夠從各類輸入源解析 XML,這些輸入源有 InputStreams、Files、URL 和 SAX InputSources,這些輸入源就是產品等級(具體的產品),不一樣的解析器(實現)就是產品族,又由於全部的解析器都服從於 JAXP 所定義的接口,因此不管具體使用哪個解析器,調用者的(客戶端)代碼都是同樣的。
當在不一樣的解析器之間進行切換時(各個解析器就是不一樣的產品族,好比有微軟的解析器,有IBM的解析器……),只須要更改系統變量的值,而不用更改任何代碼。這就是抽象工廠所帶來的好處,很是巧妙。