(五)抽象工廠模式詳解

(五)抽象工廠模式詳解

            作者:zuoxiaolong8810(左瀟龍),轉載請註明出處,特別說明:本博文來自博主原博客,爲保證新博客中博文的完整性,特複製到此留存,如需轉載請註明新博客地址即可。

            前兩章我們已經討論了兩種有關工廠的模式,今天我們來看最後一種與工廠相關的模式,抽象工廠模式。

            抽象工廠模式算是工廠相關模式的終極形態,如果各位完全理解了上一章的工廠方法模式,那麼抽象工廠模式就很好理解了。它與工廠方法唯一的區別就是工廠的接口裏是一系列創造抽象產品的方法,而不再是一個,而相應的,抽象產品也不再是一個了,而是一系列相關的產品。這其實是工廠方法模式的一種擴展不是嗎?

            通常意義來我們談到擴展,通常有兩種方式可以擴展一個接口或者類,就是繼承和組合。

            通常情況下,我們推薦使用組合擴展一個現有的類或接口,但這並非絕對,如果你擴展的子類或子接口與現有的類或接口明顯是“是一個(is a)”的關係,也就是繼承的關係,那麼使用繼承可以獲得更多的好處。

            下面我們就首先來看一下抽象工廠模式的定義以及類圖,全部引自百度百科。

            定義:爲創建一組相關或相互依賴的對象提供一個接口,而且無需指定他們的具體類。

            定義中說了,我們是要創建一個接口, 而這個接口是幹嘛的呢,前面說了,是爲了創建一組相關或者相互依賴的對象,而且還有一點就是,我們創建的對象不是具體的類,也就是說我們創建的是一個接口或者一個抽象類。

            下面我們來看看抽象工廠模式的類圖。

            我們對比下剛纔的定義,LZ給各位分析下上面的類圖,首先剛纔說了,我們要創建一個接口,這個接口就是指的Creator,而一組相關或者相互依賴的對象,就是指的ProductA和ProductB以及它們具體的實現類,而上面又提到說不是返回的具體的類,所以我們返回的應該是接口或者抽象類,那麼在上述類圖當中,則是指的ProductA和ProductB接口。

            下面LZ將上述類圖詮釋成容易理解的JAVA代碼,供各位參考。

            首先給出我們的產品族,也就是類圖中右半部分。

複製代碼
package net;

interface ProductA {

    void methodA();
}

interface ProductB {
    
    void methodB();
}

class ProductA1 implements ProductA{

    public void methodA() {
        System.out.println("產品A系列中1型號產品的方法");
    }
    
}

class ProductA2 implements ProductA{

    public void methodA() {
        System.out.println("產品A系列中2型號產品的方法");
    }
    
}

class ProductB1 implements ProductB{

    public void methodB() {
        System.out.println("產品B系列中1型號產品的方法");
    }
    
}

class ProductB2 implements ProductB{

    public void methodB() {
        System.out.println("產品B系列中2型號產品的方法");
    }
    
}
複製代碼

            結構比較清晰,下面是類圖中左半部分,首先給出工廠接口。

複製代碼
package net;

public interface Creator {

    ProductA createProductA();
    
    ProductB createProductB();
    
}
複製代碼

            下面是兩個具體的工廠實現類。

複製代碼
package net;

public class ConcreteCreator1 implements Creator{

    public ProductA createProductA() {
        return new ProductA1();
    }

    public ProductB createProductB() {
        return new ProductB1();
    }

}
package net;

public class ConcreteCreator2 implements Creator{

    public ProductA createProductA() {
        return new ProductA2();
    }

    public ProductB createProductB() {
        return new ProductB2();
    }

}
複製代碼

            這樣我們的類圖代碼就實現完畢,下面我們寫一個測試類,去調用一下,感受一下抽象工廠模式的客戶端調用方式。

複製代碼
package net;


public class Client {

    public static void main(String[] args) throws Exception {
        Creator creator = new ConcreteCreator1();
        ProductA productA = creator.createProductA();
        ProductB productB = creator.createProductB();
        productA.methodA();
        productB.methodB();
        
        creator = new ConcreteCreator2();
        productA = creator.createProductA();
        productB = creator.createProductB();
        productA.methodA();
        productB.methodB();
    }
}
複製代碼

            在過程當中,我們切換過一次工廠實現類,而下面的代碼是一模一樣的,但是我們使用的就是另一套產品實現體系了,我們看運行結果。

            上面的代碼比較簡單,結構很清晰但不太容易理解,因爲它全部是抽象的表示,與實際聯繫不上,所以也會對各位的理解造成阻礙,下面我們就一起討論一個現有的例子,去加深去抽象工廠模式的理解。

            上一章我們介紹了iterable接口,它可以製作iterator,iterator方法是一個工廠方法,用於讓子類製作一系列的iterator,不過java集合框架一般都將iterator的實現作爲內部類出現,所以我們從未見過LZ上章提到的ListIterator和KeyIterator的實現類,但它們確實存在於JAVA的集合框架,並且它們的實現類被封裝在相應的抽象類或者具體的容器實現類中。

            oracle公司爲何不讓我們看到這些iterator的實現類呢?其實原因很簡單,一是怕我們在寫程序的時候依賴於這些iterator的實現類,二是這些迭代器的實現都要依賴於當前的容器實現,我們假設有一天JDK中的集合框架要升級,要替換掉某個iterator的實現,換做一種更快的迭代方式(假設存在這種方式),那麼以前使用特定迭代器的程序可能就無法正常運行了。當然大部分的情況下,oracle不會將現有的類剔除,但是會加上@Deprecated註解,來標識這是一個過時的東西,不再推薦你使用。但就算是這樣,還是有缺點,就是JDK升級以後,你享受不到JDK集合框架速度上的提升,除非你將所有你使用過具體的Iterator的地方全部手動替換掉。

            上述大致描述了下集合框架設計時對iterator處理方式的初衷,從中可以看出抽象工廠模式就是爲了解決抽象產品不再是一個的時候的問題。因爲不管是簡單工廠,還是工廠方法,都有一個缺陷,那就是整個模式當中只能有一個抽象產品,所以直觀的,你在工廠方法模式中再添加一個創造抽象產品的方法就是抽象工廠模式了,相應的當然還有添加一個抽象產品,還有一系列具體的該抽象產品的實現。

            在集合框架裏,有一個不太明顯的抽象工廠模式,就是List接口,它在iterable的基礎上,擴展了一個創建產品的方法,本次以List接口爲例,我們來看看List接口的源碼。

複製代碼
package java.util;

public interface List<E> extends Collection<E> {
    
    Iterator<E> iterator();//一種產品

    Object[] toArray();

    <T> T[] toArray(T[] a);

    ListIterator<E> listIterator();//另外一種產品

    ListIterator<E> listIterator(int index);

}
複製代碼

               LZ去掉了List接口中的很多方法,一是爲了節省版面,另外是爲了更清晰,我們主要關注iterator和listIterator方法,LZ在上面加了標註。

               其中ListIterator是Iterator的子接口,但歸根到底,它其實屬於另外一種產品,爲什麼這麼說呢,ListIterator不是Iterator的子接口嗎,怎麼能算是另外一種產品呢?這是因爲我們listIterator方法的返回類型是ListIterator,而不是Iterator,所以兩者的功能是不同的,比如ListIterator還可以向前移動。

               我們可以認爲這兩個方法產生的一個是隻能向後移動的迭代器,一個是可以前後移動的迭代器,這算是兩種產品,相當於上面的ProductA和ProductB。

               這個設計可以看做是一個抽象工廠模式,List接口定義了兩種生產不同產品的方法,這屬於兩個系列的產品,不過由於產品接口本身的繼承關係,兩者的實現類也會被做成繼承的關係。下面給出上面提到的接口的UML圖。


                 這個圖看起來有點複雜,各位可以和上面標準的抽象工廠模式類圖對比一下,下面LZ來解釋一下在抽象工廠模式當中,上述幾個類都代表的什麼角色。

                 1.List,是抽象工廠的角色,它有兩個製造產品的方法,iterator和listIterator,相當於Creator。

                 2.ListIterator和Iterator都是抽象產品,相當於ProductA和ProductB。其中ListIterator有兩個實現類,分別是AbstractList.ListItr和LinkedList.ListItr,相當於ProductA1和ProductA2。Iterator的實現類爲AbstractList.Itr,相當於ProductB1,但是沒有B2。

                 3.LinkedList是其中一個具體的工廠類,相當於ConcreteCreator1,實現抽象工廠List,它製造的兩個具體產品分別是LinkedList.ListItr和AbstractList.Itr。

                 4.同樣的,ArrayList也是一個具體的工廠類,相當於ConcreteCreator2,實現抽象工廠List,它製造的兩個具體產品分別是AbstractList.ListItr和AbstractList.Itr。

                結合上一章工廠方法模式,我們來分析一下工廠方法模式和抽象工廠模式二者的關係。

                Iterable接口是List的父接口,所以它只負責一個產品Iterator的製造,所以是工廠方法模式,而List接口擴展了Iterable接口,又添加了一個製造產品的方法,即又添加了一個系列的產品,所以就成爲了抽象工廠模式。

              LZ下面給出上述兩個類圖的對應關係,會讓各位看的更加清晰:

              1.Creator=List

              2.ConcreteCreator1=ArrayList

              3.ConcreteCreator2=LinkedList

              4.ProductA=Iterator

              5.ProductB=ListIterator

              6.ProductA1=AbstractList.Itr

              7.ProductA2=無(具體的A產品2在第一個類圖中是沒有的,但這並不影響整個體系)

              8.ProductB1=AbstractList.ListItr

              9.ProductB2=LinkedList.ListItr

              ArrayList和LinkedList分別是List接口的兩種實現,前者是基於數組操作,後者是基於鏈表。兩者都可以產生Iterator和ListIterator,而Iterator的實現都是在AbstractList中實現的,是一樣的處理方式,而對於ListIterator的實現卻不相同,AbstractList.ListItr是基於數組的操作,LinkedList.ListItr是基於鏈表的操作方式。

              所以抽象工廠模式一般是爲了處理抽象產品多於一個的問題,而且這些產品多數情況下是有關係的,像上述JAVA集合框架的例子當中,Iterator和ListIterator就是繼承的關係,大部分情況下,很少會使用抽象工廠模式去創造一批毫無關係的產品。

              基於抽象工廠一旦定義,抽象產品的個數就已經固定,所以最好在抽象產品的個數不太會變化的情況下使用抽象工廠模式,當然,我們可以使用繼承去彌補抽象工廠模式的這一不足,創造另外一個繼承體系去擴展現有的框架。

              下面LZ給出簡單工廠模式,工廠方法模式一直到抽象工廠模式的演變過程,三者是由簡到繁的關係。由於三者都已經詳細的解釋過,所以此處不再多做解釋,留給各位讀者自己思考它們的進化過程,首先LZ給出簡單工廠的具體代碼。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//產品工廠(下一步就是它的進化,就變成了工廠方法模式)
public class ProductFactory {

    private ProductFactory(){}
    
    public static Product getProduct(String productName){
        if (productName.equals("A")) {
            return new ProductA();
        }else if (productName.equals("B")) {
            return new ProductB();
        }else {
            return null;
        }
    }
}
複製代碼

               LZ在上面加了簡單的註釋,下面LZ給出工廠方法模式的代碼,注意,前面有關產品的類和接口是不變的。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//將簡單工廠中的工廠給抽象成接口
interface Factory{
    Product getProduct();
}
//具體的工廠A,創造產品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具體的工廠B,創造產品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}
複製代碼

                  可以看到,產品部分並沒有變化,只是將簡單工廠中的工廠類抽象成接口,並給相應產品添加相應的工廠類,就進化成了工廠方法模式。下面我們再看工廠方法如何進化成抽象工廠模式。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//多了一個抽象產品1
interface Product1{}

//具體產品1
class Product1A implements Product1{}
class Product1B implements Product1{}

//原有的工廠方法模式的工廠裏添加一個方法
interface Factory{
    Product getProduct();
    //添加另外一個產品族的創造方法
    Product1 getProduct1();
}
//具體的工廠A,創造產品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    //添加相應的實現
    public Product1 getProduct1() {
        return new Product1A();
    }
    
}
//具體的工廠B,創造產品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    //添加相應的實現
    public Product1 getProduct1() {
        return new Product1B();
    }
    
}
複製代碼

                  與工廠方法對比下就發現,多了一個產品系列叫Product1,工廠接口裏多了一個方法,叫getProduct1,所以抽象工廠模式就是工廠方法模式添加了抽象產品所演變而來的。
                  有關工廠的三個模式到這裏就全部介紹完了,三者有着很大的關聯和明顯的關係,要想靈活運用這三種設計模式,還是要徹底理解它們所針對的問題以及三者的關係。下面羅列下這三種設計模式依次進化的原因。

                  1,首先從簡單工廠進化到工廠方法,是因爲工廠方法彌補了簡單工廠對修改開放的弊端,即簡單工廠違背了開閉原則。

                  2,從工廠方法進化到抽象工廠,是因爲抽象工廠彌補了工廠方法只能創造一個系列的產品的弊端。

                  各位可以思考下,假設我們不使用抽象工廠模式,改用工廠方法去處理抽象工廠中多產品的問題,如何處理呢?其實很簡單,就是有幾個產品系列,我們就造幾個工廠方法模式就可以了,只不過這樣處理未免太不優雅,就像下面這樣。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//工廠接口
interface Factory{
    Product getProduct();
}

//具體的工廠A,創造產品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具體的工廠B,創造產品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   以上是一個產品的工廠方法  */

//抽象產品1
interface Product1{}

//具體產品1
class Product1A implements Product1{}
class Product1B implements Product1{}

//工廠接口1
interface Factory1{
    Product1 getProduct1();
}

//具體的工廠1A,創造產品1A
class Factory1A implements Factory1{

    public Product1 getProduct1() {
        return new Product1A();
    }
    
}
//具體的工廠1B,創造產品1B
class Factory1B implements Factory1{

    public Product1 getProduct1() {
        return new Product1B();
    }
    
}
複製代碼

                以上用兩個工廠方法模式,代替了抽象工廠模式,那麼可想而知,假設又多了一個產品Product2,那麼我們還需要再建立一套工廠方法模式,這顯然會大大增加系統的複雜性,而且也不易於客戶端操作。

                不過這也不一定就不可以,假設我們上面Product和Factory接口包括它們的一套實現是現有的,並且我們無法改變,比如是一個第三方的jar包提供的。那麼爲了擴展這個第三方的jar包,我們可以將jar包中的工廠方法模式擴展成爲抽象工廠來達到我們擴展現有類功能的目的,就像下面這樣。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//工廠接口
interface Factory{
    Product getProduct();
}

//具體的工廠A,創造產品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具體的工廠B,創造產品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   假設以上是一個第三方jar包中的工廠方法模式,我們無法改動源碼   */

//我們自己特有的產品
interface MyProduct{}

//我們自己特有的產品實現
class MyProductA implements MyProduct{}
class MyProductB implements MyProduct{}

//擴展原有的工廠接口
interface MyFactory extends Factory{
    MyProduct getMyProduct();
}

//我們自己特有的工廠A,擴展自原有的工廠A,並且實現獲得我們自己特有產品的接口方法
class MyFactoryA extends FactoryA implements MyFactory{

    public MyProduct getMyProduct() {
        return new MyProductA();
    }
    
}
//同A
class MyFactoryB extends FactoryB implements MyFactory{

    public MyProduct getMyProduct() {
        return new MyProductB();
    }
    
}
複製代碼

                  這樣我們就可以得到我們自己特有的抽象工廠和使用我們自己特有的產品了,並且我們自己的抽象工廠還兼併了第三方jar包中的產品,例如,我們可以使用MyFactoryA獲得jar包中的ProductA產品等。

                  上面的做法相當於我們從現有的體系當中,擴展出一套我們自己的繼承體系,這樣做的好處是我們可以完整的複用jar包中的各個類功能,缺點是繼承會導致系統的複雜性增加,耦合度相對較高。

                  所以我們還可以有另外一種做法,就是創造我們自己的一套獨有的工廠方法模式,這套體系與jar包中的類和接口毫無關係,我們再使用一個組合工廠將二者結合起來,就像下面這樣。

複製代碼
//抽象產品
interface Product{}

//具體產品
class ProductA implements Product{}
class ProductB implements Product{}

//工廠接口
interface Factory{
    Product getProduct();
}

//具體的工廠A,創造產品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具體的工廠B,創造產品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   假設以上是一個第三方jar包中的工廠方法模式,我們無法改動源碼   */

//我們自己特有的產品
interface MyProduct{}

//我們自己特有的產品實現
class MyProductA implements MyProduct{}
class MyProductB implements MyProduct{}

//我們自己的工廠接口
interface MyFactory{
    MyProduct getMyProduct();
}

//我們自己特有的工廠A,產生產品A
class MyFactoryA implements MyFactory{
    
    public MyProduct getMyProduct() {
        return new MyProductA();
    }
    
}

//我們自己特有的工廠B,產生產品B
class MyFactoryB implements MyFactory{
    
    public MyProduct getMyProduct() {
        return new MyProductB();
    }
    
}

/*  到這裏是我們自己的一套工廠方法模式,去創造我們自己的產品,以下我們將以上二者組合   */

//我們使用組合的方式將我們的產品系列和jar包中的產品組合起來
class AssortedFactory implements MyFactory,Factory{
    
    MyFactory myFactory;
    Factory factory;
    
    public AssortedFactory(MyFactory myFactory, Factory factory) {
        super();
        this.myFactory = myFactory;
        this.factory = factory;
    }

    public Product getProduct() {
        return factory.getProduct();
    }

    public MyProduct getMyProduct() {
        return myFactory.getMyProduct();
    }
    
}
複製代碼

                    可以看到,組合的工廠AssortedFactory集成了我們自己的工廠和jar包中的工廠兩者的功能。這樣做則會非常靈活,因爲我們的一套體系不再依賴於jar包中的類或接口而存在,哪怕是jar包中的類改變或者不在了,我們自己的這一套依舊可以獨立存在。

                    從上面就可以看出,我們在處理很多問題的時候其實是有很多種方式的,而且每一種方式可能都有各自的好處和壞處,很難去判斷說那一種方式是最好的,而且也根本就沒有這個說法,所以我們能做的,就是根據實際的情況去掂量各個方式的利弊,從而選擇出一種更適合當前情況的處理方式。

                    本期抽象工廠模式就到這裏了,希望各位看到這裏對三個與工廠相關的模式有一定自己的理解,並且可以靈活使用,達到無模式就是最好的模式的境界,甚至,我們可以把我們自己寫的東西起個名字,冒充設計模式,比如最後一種我們叫它組合工廠模式。當然,LZ本人並未達到無模式的境界,正在與各位一起努力。

發佈了13 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章