建立產品族的方式——抽象工廠模式

前言

繼續接 建立多個「產品」的方式——工廠方法模式總結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();
    }
}

當之後引入溫室水果的時候,除了必需要創建溫室蘋果類,溫室香蕉類去繼承對應的水果抽象類以外,只須要再創建一個溫室水果工廠類去實現抽象工廠接口便可,已經存在的代碼不須要修改。

類圖以下

一、抽象工廠模式中的方法對應產品等級結構,具體子工廠對應不一樣的產品族。

二、產品族,簡單理解就是不一樣的產品類型

三、產品等級結構,簡單理解就是一個產品類型裏的具體的產品

抽象工廠模式的優缺點

優勢

一、分離接口和實現

客戶端使用抽象工廠來建立須要的對象,而客戶端根本就不知道具體的實現是誰,客戶端只是面向產品的接口編程而已。也就是說,客戶端從具體的產品實現中解耦

二、使切換產品族變得容易

因一個具體的工廠實現表明的是一個產品族,切換產品族只須要切換一下具體工廠

缺點

不太容易擴展新的產品,如須要給整個產品族添加一個新的產品,那麼就須要修改抽象工廠(增長新的接口方法),這樣就會致使修改全部的工廠實現類。好比增長橘子這個產品等級……

也就是說,縱向不怕擴展,橫向不方便擴展

抽象工廠模式和工廠方法模式對比

抽象工廠模式與工廠方法模式的最大區別就在於,工廠方法模式針對的是一個產品等級結構(蘋果,鴨梨,橘子……)

而抽象工廠模式則須要面對多個產品等級結構(進口、國產、溫室栽培……的蘋果,鴨梨,橘子……)

什麼狀況下使用抽象工廠模式?

系統的產品有多於一個的產品族,而系統只消費其中某一族的產品

JDK中使用抽象工廠模式的例子

常見有: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的解析器……),只須要更改系統變量的值,而不用更改任何代碼。這就是抽象工廠所帶來的好處,很是巧妙。

相關文章
相關標籤/搜索