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


前言

繼續接 創建多個“產品”的方式——工廠方法模式總結

現在又有了新的需求:果廠里新進了一批進口水果:進口香蕉,進口蘋果,進口梨,同樣的是需要采集水果,之前的程序只是對工廠進行了抽象,使得不同的產品對應各自的工廠,而產品僅是國內水果,現在涉及到了進口水果,現在有了兩大類的產品,每個產品又分為不同的等級,我們叫它產品族。

產品族概念

所謂產品族,是指位於不同產品等級結構中,功能相關聯的產品組成的家族。

1、每一個產品族中含有產品的數目與產品等級結構的數目是相等的

2、產品的等級結構與產品族將產品按照不同方向划分,形成一個二維的坐標系。

橫軸表示產品的等級結構,縱軸表示產品族。

3、只要指明一個產品所處的產品族以及它所屬的等級結構,就可以唯一的確定這個產品。

1、國產水果和進口水果就是產品族(縱坐標)

2、蘋果,香蕉,鴨梨等,就是產品的等級結構,具體每個產品族的等級

這個業務就相對復雜了,所以之前的模式就變得不好用。如果硬要使用,那就是在具體的蘋果工廠類里再增加一個新的方法——返回進口蘋果類的實例,同時也為進口蘋果增加對應的進口蘋果類,同理對於香蕉,鴨梨也是一樣的,好像也沒什么問題。

引入抽象工廠模式

現在需求又變了,果園廠增加了溫室種植技術,有了一種新的產品——溫室種植水果。如何寫代碼?

按照工廠方法模式的寫法,自然就要在具體的蘋果工廠類里再增加一個新的方法——返回溫室蘋果類的實例,同時也要增加溫室蘋果這個新的產品……其他水果工廠類是一樣的做法。這明顯違背了 OCP 原則,而且蘋果工廠類既能生成進口蘋果也能生產國產蘋果,違背了單一職責原則。此時抽象工廠模式就派上了用場。

1、抽象工廠模式是所有形態的工廠模式中最為抽象和一般性的。

2、抽象工廠模式可以向客戶端提供一個接口,使得客戶端在不必指定產品的具體類型的情況下,能夠創建多個產品族的產品對象。

抽象工廠模式實現

水果廠有進口水果,國產水果兩個產品族,而具體獲得哪個產品族是客戶端調用決定的。比如,進口蘋果,進口橘子,國產蘋果,國產橘子等……蘋果,橘子是產品族(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();
    }
}

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

類圖如下

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

2、產品族,簡單理解就是不同的產品類型

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

抽象工廠模式的優缺點

優點

1、分離接口和實現

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

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

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

缺點

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

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

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

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

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

什么情況下使用抽象工廠模式?

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

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

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM