設計模式(五)抽象工廠模式詳解

楓奇發表於2017-06-30

   

 轉載自 http://blog.csdn.net/zuoxiaolong8810/article/details/9079195

    作者:zuoxiaolong8810(左瀟龍),轉載請註明出處。


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

            通常意義來我們談到擴充套件,通常有兩種方式可以擴充套件一個介面或者類,就是繼承和組合。

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

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

            定義:為建立一組相關或相互依賴的物件提供一個介面,而且無需指定他們的具體類。

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

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

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

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

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

[java] view plain copy
  1. package net;  
  2.   
  3. interface ProductA {  
  4.   
  5.     void methodA();  
  6. }  
  7.   
  8. interface ProductB {  
  9.       
  10.     void methodB();  
  11. }  
  12.   
  13. class ProductA1 implements ProductA{  
  14.   
  15.     public void methodA() {  
  16.         System.out.println("產品A系列中1型號產品的方法");  
  17.     }  
  18.       
  19. }  
  20.   
  21. class ProductA2 implements ProductA{  
  22.   
  23.     public void methodA() {  
  24.         System.out.println("產品A系列中2型號產品的方法");  
  25.     }  
  26.       
  27. }  
  28.   
  29. class ProductB1 implements ProductB{  
  30.   
  31.     public void methodB() {  
  32.         System.out.println("產品B系列中1型號產品的方法");  
  33.     }  
  34.       
  35. }  
  36.   
  37. class ProductB2 implements ProductB{  
  38.   
  39.     public void methodB() {  
  40.         System.out.println("產品B系列中2型號產品的方法");  
  41.     }  
  42.       
  43. }  

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

[java] view plain copy
  1. package net;  
  2.   
  3. public interface Creator {  
  4.   
  5.     ProductA createProductA();  
  6.       
  7.     ProductB createProductB();  
  8.       
  9. }  

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

[java] view plain copy
  1. package net;  
  2.   
  3. public class ConcreteCreator1 implements Creator{  
  4.   
  5.     public ProductA createProductA() {  
  6.         return new ProductA1();  
  7.     }  
  8.   
  9.     public ProductB createProductB() {  
  10.         return new ProductB1();  
  11.     }  
  12.   
  13. }  
[java] view plain copy
  1. package net;  
  2.   
  3. public class ConcreteCreator2 implements Creator{  
  4.   
  5.     public ProductA createProductA() {  
  6.         return new ProductA2();  
  7.     }  
  8.   
  9.     public ProductB createProductB() {  
  10.         return new ProductB2();  
  11.     }  
  12.   
  13. }  

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

[java] view plain copy
  1. package net;  
  2.   
  3.   
  4. public class Client {  
  5.   
  6.     public static void main(String[] args) throws Exception {  
  7.         Creator creator = new ConcreteCreator1();  
  8.         ProductA productA = creator.createProductA();  
  9.         ProductB productB = creator.createProductB();  
  10.         productA.methodA();  
  11.         productB.methodB();  
  12.           
  13.         creator = new ConcreteCreator2();  
  14.         productA = creator.createProductA();  
  15.         productB = creator.createProductB();  
  16.         productA.methodA();  
  17.         productB.methodB();  
  18.     }  
  19. }  

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

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

            上一章我們介紹了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介面的原始碼。

[java] view plain copy
  1. package java.util;  
  2.   
  3. public interface List<E> extends Collection<E> {  
  4.       
  5.     Iterator<E> iterator();//一種產品  
  6.   
  7.     Object[] toArray();  
  8.   
  9.     <T> T[] toArray(T[] a);  
  10.   
  11.     ListIterator<E> listIterator();//另外一種產品  
  12.   
  13.     ListIterator<E> listIterator(int index);  
  14.   
  15. }  

               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給出簡單工廠的具體程式碼。

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //產品工廠(下一步就是它的進化,就變成了工廠方法模式)  
  9. public class ProductFactory {  
  10.   
  11.     private ProductFactory(){}  
  12.       
  13.     public static Product getProduct(String productName){  
  14.         if (productName.equals("A")) {  
  15.             return new ProductA();  
  16.         }else if (productName.equals("B")) {  
  17.             return new ProductB();  
  18.         }else {  
  19.             return null;  
  20.         }  
  21.     }  
  22. }  

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

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //將簡單工廠中的工廠給抽象成介面  
  9. interface Factory{  
  10.     Product getProduct();  
  11. }  
  12. //具體的工廠A,創造產品A  
  13. class FactoryA implements Factory{  
  14.   
  15.     public Product getProduct() {  
  16.         return new ProductA();  
  17.     }  
  18.       
  19. }  
  20. //具體的工廠B,創造產品B  
  21. class FactoryB implements Factory{  
  22.   
  23.     public Product getProduct() {  
  24.         return new ProductB();  
  25.     }  
  26.       
  27. }  

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

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //多了一個抽象產品1  
  9. interface Product1{}  
  10.   
  11. //具體產品1  
  12. class Product1A implements Product1{}  
  13. class Product1B implements Product1{}  
  14.   
  15. //原有的工廠方法模式的工廠裡新增一個方法  
  16. interface Factory{  
  17.     Product getProduct();  
  18.     //新增另外一個產品族的創造方法  
  19.     Product1 getProduct1();  
  20. }  
  21. //具體的工廠A,創造產品A  
  22. class FactoryA implements Factory{  
  23.   
  24.     public Product getProduct() {  
  25.         return new ProductA();  
  26.     }  
  27.     //新增相應的實現  
  28.     public Product1 getProduct1() {  
  29.         return new Product1A();  
  30.     }  
  31.       
  32. }  
  33. //具體的工廠B,創造產品B  
  34. class FactoryB implements Factory{  
  35.   
  36.     public Product getProduct() {  
  37.         return new ProductB();  
  38.     }  
  39.     //新增相應的實現  
  40.     public Product1 getProduct1() {  
  41.         return new Product1B();  
  42.     }  
  43.       
  44. }  

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

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

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

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

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //工廠介面  
  9. interface Factory{  
  10.     Product getProduct();  
  11. }  
  12.   
  13. //具體的工廠A,創造產品A  
  14. class FactoryA implements Factory{  
  15.   
  16.     public Product getProduct() {  
  17.         return new ProductA();  
  18.     }  
  19.       
  20. }  
  21. //具體的工廠B,創造產品B  
  22. class FactoryB implements Factory{  
  23.   
  24.     public Product getProduct() {  
  25.         return new ProductB();  
  26.     }  
  27.       
  28. }  
  29.   
  30. /*   以上是一個產品的工廠方法  */  
  31.   
  32. //抽象產品1  
  33. interface Product1{}  
  34.   
  35. //具體產品1  
  36. class Product1A implements Product1{}  
  37. class Product1B implements Product1{}  
  38.   
  39. //工廠介面1  
  40. interface Factory1{  
  41.     Product1 getProduct1();  
  42. }  
  43.   
  44. //具體的工廠1A,創造產品1A  
  45. class Factory1A implements Factory1{  
  46.   
  47.     public Product1 getProduct1() {  
  48.         return new Product1A();  
  49.     }  
  50.       
  51. }  
  52. //具體的工廠1B,創造產品1B  
  53. class Factory1B implements Factory1{  
  54.   
  55.     public Product1 getProduct1() {  
  56.         return new Product1B();  
  57.     }  
  58.       
  59. }  

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

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

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //工廠介面  
  9. interface Factory{  
  10.     Product getProduct();  
  11. }  
  12.   
  13. //具體的工廠A,創造產品A  
  14. class FactoryA implements Factory{  
  15.   
  16.     public Product getProduct() {  
  17.         return new ProductA();  
  18.     }  
  19.       
  20. }  
  21. //具體的工廠B,創造產品B  
  22. class FactoryB implements Factory{  
  23.   
  24.     public Product getProduct() {  
  25.         return new ProductB();  
  26.     }  
  27.       
  28. }  
  29.   
  30. /*   假設以上是一個第三方jar包中的工廠方法模式,我們無法改動原始碼   */  
  31.   
  32. //我們自己特有的產品  
  33. interface MyProduct{}  
  34.   
  35. //我們自己特有的產品實現  
  36. class MyProductA implements MyProduct{}  
  37. class MyProductB implements MyProduct{}  
  38.   
  39. //擴充套件原有的工廠介面  
  40. interface MyFactory extends Factory{  
  41.     MyProduct getMyProduct();  
  42. }  
  43.   
  44. //我們自己特有的工廠A,擴充套件自原有的工廠A,並且實現獲得我們自己特有產品的介面方法  
  45. class MyFactoryA extends FactoryA implements MyFactory{  
  46.   
  47.     public MyProduct getMyProduct() {  
  48.         return new MyProductA();  
  49.     }  
  50.       
  51. }  
  52. //同A  
  53. class MyFactoryB extends FactoryB implements MyFactory{  
  54.   
  55.     public MyProduct getMyProduct() {  
  56.         return new MyProductB();  
  57.     }  
  58.       
  59. }  

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

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

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

[java] view plain copy
  1. //抽象產品  
  2. interface Product{}  
  3.   
  4. //具體產品  
  5. class ProductA implements Product{}  
  6. class ProductB implements Product{}  
  7.   
  8. //工廠介面  
  9. interface Factory{  
  10.     Product getProduct();  
  11. }  
  12.   
  13. //具體的工廠A,創造產品A  
  14. class FactoryA implements Factory{  
  15.   
  16.     public Product getProduct() {  
  17.         return new ProductA();  
  18.     }  
  19.       
  20. }  
  21. //具體的工廠B,創造產品B  
  22. class FactoryB implements Factory{  
  23.   
  24.     public Product getProduct() {  
  25.         return new ProductB();  
  26.     }  
  27.       
  28. }  
  29.   
  30. /*   假設以上是一個第三方jar包中的工廠方法模式,我們無法改動原始碼   */  
  31.   
  32. //我們自己特有的產品  
  33. interface MyProduct{}  
  34.   
  35. //我們自己特有的產品實現  
  36. class MyProductA implements MyProduct{}  
  37. class MyProductB implements MyProduct{}  
  38.   
  39. //我們自己的工廠介面  
  40. interface MyFactory{  
  41.     MyProduct getMyProduct();  
  42. }  
  43.   
  44. //我們自己特有的工廠A,產生產品A  
  45. class MyFactoryA implements MyFactory{  
  46.       
  47.     public MyProduct getMyProduct() {  
  48.         return new MyProductA();  
  49.     }  
  50.       
  51. }  
  52.   
  53. //我們自己特有的工廠B,產生產品B  
  54. class MyFactoryB implements MyFactory{  
  55.       
  56.     public MyProduct getMyProduct() {  
  57.         return new MyProductB();  
  58.     }  
  59.       
  60. }  
  61.   
  62. /*  到這裡是我們自己的一套工廠方法模式,去創造我們自己的產品,以下我們將以上二者組合   */  
  63.   
  64. //我們使用組合的方式將我們的產品系列和jar包中的產品組合起來  
  65. class AssortedFactory implements MyFactory,Factory{  
  66.       
  67.     MyFactory myFactory;  
  68.     Factory factory;  
  69.       
  70.     public AssortedFactory(MyFactory myFactory, Factory factory) {  
  71.         super();  
  72.         this.myFactory = myFactory;  
  73.         this.factory = factory;  
  74.     }  
  75.   
  76.     public Product getProduct() {  
  77.         return factory.getProduct();  
  78.     }  
  79.   
  80.     public MyProduct getMyProduct() {  
  81.         return myFactory.getMyProduct();  
  82.     }  
  83.       
  84. }  

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

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

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

                    感謝各位的收看。

       

相關文章