【設計模式 - 1】之工廠模式(Factory)

1      模式簡介

1.1    工廠模式作用

l  工廠模式解決的是“使用new關鍵字獲取對象造成松耦合”的問題。

工廠模式主要是爲創建對象提供過渡接口,以便將創建對象的具體過程屏蔽隔離起來,達到提高靈活性的目的。

 

1.2    工廠模式分類

1.2.1  分類

工廠模式可以分爲三類:

簡單工廠模式(SimpleFactory

工廠方法模式(FactoryMethod

抽象工廠方法(AbstractFactory

 

1.2.2  分類簡介

注:這裏將簡單工廠模式看作工廠方法模式的一個特例

1.2.2.1       工廠方法模式

一個抽象產品類,可以派生出多個具體產品類;

一個抽象工廠類,可以派生出多個具體工廠類;

每個具體工廠類只能創建一個具體產品類的實例。

 

1.2.2.2       抽象工廠模式

多個抽象產品類,每個抽象產品類可以派生出多個具體產品類;

一個抽象工廠類,可以派生出多個具體工廠類;

每個具體工廠類可以創建多個具體產品類的實例。

 

1.2.3  區別

工廠方法模式只有一個抽象產品類,而抽象工廠模式有多個;

工廠方法模式的具體工廠類只能創建一個具體產品類的實例,而抽象工廠模式可以創建多個。

 

2      簡單工廠模式

2.1    簡介


簡單工廠模式的工作流程如上圖所示:汽車工廠中有創建汽車的方法,用戶只需要將汽車的型號作爲參數傳到工廠中(告訴工廠自己想要造哪種型號的汽車),工廠會根據參數創建不同的汽車對象後返回給用戶。

 

可見,在工廠模式中,用戶不需要知道汽車具體是如何製造的,他們只需要告訴工廠自己想要哪個型號的汽車,其他的工作交給工廠去做即可。

 

簡單工廠模式主要包括三部分:

工廠角色:相當於上圖中的FactoryBMW,具有一定的判斷邏輯;

抽象產品角色:相當於上圖中的BMW,一般是具體商品繼承的父類後實現的接口;

具體產品角色:相當於上圖中的BMW532BMW320,是抽象產品類的子類或實現類。

 

2.2    分析

設計模式的最終目標:讓程序“對擴展開放,對修改關閉(開閉原則)”。

 

當有新的車型加入工廠生產線後,不僅需要新加一個新的車型類,還需要在工廠中改變車型的判斷邏輯,這是不符合開閉原則的。

 

要解決這個問題,需要工廠方法模式。

 

2.3    代碼

程序目錄架構:

 

抽象汽車產品類BMW.java類中的代碼:

public abstract class BMW {
      public abstract void introduce();
}

具體汽車產品類BMW523.java和BMW320.java中的代碼(以BMW523.java爲例):

public class BMW523 extends BMW {
      public BMW523() {
      }
 
      @Override
      public void introduce() {
           System.out.println("這是一輛BMW523型號車");
      }
}
汽車工廠類FactoryBMW.java中的代碼:

public class FactoryBMW {
      public static final int TYPE_BMW523 = 1;
      public static final int TYPE_BMW320 = 2;
 
      public static BMW createBMW(int type) {
           switch (type) {
           case TYPE_BMW523:
                 return new BMW523();
           case TYPE_BMW320:
                 return new BMW320();
           }
           return null;
      }
}
測試類Test.java中的代碼:

public class Test {
      private static BMW bmw;
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("請選擇要生產的汽車的型號:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           bmw = FactoryBMW.createBMW(type);
           if (bmw != null) {
                 bmw.introduce();
           }else {
                 System.out.println("沒有這個車型!");
           }
 
           scanner.close();
      }
}
運行結果:


3      工廠方法模式

3.1    簡介

工廠方法模式的工作流程如上圖所示:將簡單工廠模式中的工廠類FactoryBMW定義成接口,每種車型都定義一個新的工廠類,用來專門生產BMW類的某個車型子類。這樣,工廠可以擴展,就不需要修改原來的代碼了。

 

工廠方法模式主要包括四部分:

l  抽象工廠角色:相當於上圖中的FactoryBMW,是所有具體工廠必須實現或繼承的接口或父類;

l  具體工廠角色:相當於上圖中的FactoryBMW320和FactoryBMW523,它們含有具體的與業務邏輯有關的代碼,用來創建具體的產品對象;

l  抽象產品角色:相當於上圖中的BMW,是具體產品的接口或父類;

l  具體產品角色:相當於上圖中的BMW523和BMW320,是具體的產品類。

3.2    分析

相比於簡單工廠模式用一個工廠來創建所有車型,工廠方法模式將工廠生產汽車的方法下放到其子類中,分擔了工廠類承擔的壓力。

 

當有新的車型加入工廠生產線時,只需要創建具體的工廠類和車型類即可,不需要修改現有的代碼,讓程序變得更加靈活,可見,工廠方法模式是符合開閉原則的。

 

但是工廠方法模式也有一定的弊端:這種模式使得類的數量成倍增長,當有很多車型時,就會有很多工廠類來生產這些車型,這不是我們所希望的。這個問題的一個解決方案是將工廠方法模式與簡單工廠模式結合使用,創建方法比較相似的車型合併到一個簡單工廠中創建,創建方法差別較大的車型使用工廠方法模式分到不同的工廠中創建。

 

工廠方法模式已經比較完美的對對象進行了封裝,但也不是意味着我們要對程序中的所有對象都採用工廠方法模式。在以下情況下可以考慮使用工廠方法模式:

l  當客戶不需要知道要使用的對象的創建過程時;

l  當客戶要使用的對象存在變動的可能,或者根本就不知道要使用哪個對象時。

 

3.3    代碼

程序目錄框架:

 

抽象的工廠類FactoryBMW.java中的代碼:

public abstract class FactoryBMW {
      public abstract BMW createBMW();
}
具體的工廠類FactoryBMW320.java和FactoryBMW523.java中的代碼(這裏以FactoryBMW523.java爲例):

public class FactoryBMW523 extendsFactoryBMW {
 
      @Override
      public BMW createBMW() {
           return new BMW523();
      }
}
測試類Test.java中的代碼:

public class Test {
      private static BMW bmw;
      private static FactoryBMW factory;
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("請選擇要生產的汽車的型號:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           switch (type) {
           case 1:
                 factory = new FactoryBMW523();
                 break;
           case 2:
                 factory = new FactoryBMW320();
                 break;
           default:
                 factory = null;
                 break;
           }
          
           if (factory != null) {
                 bmw = factory.createBMW();
                 bmw.introduce();
           }else {
                 System.out.println("沒有這個車型!");
           }
 
           scanner.close();
      }
}
運行結果如下圖:

3.4    擴展

從上面的代碼我們可以看到:工廠方法模式其實並沒有做到避免代碼的修改,而是將原來需要在工廠中進行的邏輯判斷拿到了測試類中進行,這是因爲我們仍然無法判斷具體需要調用哪個工廠。面對這種情況,我們可以使用反射機制來在一定程度上進行解決。具體代碼如下:

public class Test {
      private static BMW bmw;
 
      private static final String[] classes = {
                 "com.itgungnir.designpattern.factory.factorymethod.FactoryBMW523",
                 "com.itgungnir.designpattern.factory.factorymethod.FactoryBMW320" };
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("請選擇要生產的汽車的型號:");
           System.out.println("1、BMW 523");
           System.out.println("2、BMW 320");
           int type = scanner.nextInt();
 
           try {
                 Class<?>factory = Class.forName(classes[type - 1]);
                 MethodcreateBMW = factory.getMethod("createBMW");
                 bmw = (BMW) createBMW.invoke(factory.newInstance());
           }catch (ClassNotFoundException e) {
                 e.printStackTrace();
           }catch (NoSuchMethodException e) {
                 e.printStackTrace();
           }catch (SecurityException e) {
                 e.printStackTrace();
           }catch (IllegalAccessException e) {
                 e.printStackTrace();
           }catch (IllegalArgumentException e) {
                 e.printStackTrace();
           }catch (InvocationTargetException e) {
                 e.printStackTrace();
           }catch (InstantiationException e) {
                 e.printStackTrace();
           }
 
           if (bmw != null) {
                 bmw.introduce();
           }else {
                 System.out.println("沒有這個車型!");
           }
 
           scanner.close();
      }
}
這樣,當有新的車型和工廠加入到生產線之後,除了創建具體工廠類和具體車型類,只需要在字符串數組classes中加入新創建的具體工廠類的包地址即可。這樣可以將代碼的改動降到最小。

 

注:關於反射機制的介紹可以參考:【JAVA - 基礎】之反射的原理與應用

4      抽象工廠模式

4.1    簡介

抽象工廠模式的工作流程和工廠方法模式的工作流程基本相似,唯一的不同是一個工廠生產的是一個產品的不同組件。下圖中,一個具體工廠除了可以生產汽車之外,還可以生產與該型號汽車對應的空調。(本DEMO的前提是每個車型的汽車都有一款對應的空調,一個車型的汽車不能搭載另一個車型的汽車的空調)

 

抽象工廠模式的組成部分和工廠方法模式的組成部分相同。

4.2    分析

從上圖中可以看到,抽象工廠模式的適用場景是系統中存在多個系列的產品(如汽車和空調)。實際上,抽象工廠模式和工廠方法模式的區別就在於需要創建的對象的複雜程度。用這個例子來說,就是當我們只需要生產汽車的時候,就可以選擇工廠方法模式;當我們既需要生產汽車又需要生產空調時,就可以選擇抽象工廠模式。

 

用一句話概括抽象工廠方法:給客戶端提供一個接口,可以創建多個產品族中的產品對象。

4.3    代碼

程序目錄框架:

 

空調抽象類AC.java中的代碼:

public abstract class AC {
      public abstract void introduce();
}
具體的空調類AC523.javaAC320.java中的代碼(這裏以AC523.java爲例):

public class AC523 extends AC {
 
      @Override
      public void introduce() {
           System.out.println("BMW523型號的汽車成功搭載了AC523型號的空調");
      }
}
抽象的工廠接口Factory.java中的代碼:

public interface Factory {
      BMWcreateBMW();
 
      ACcreateAC();
}
具體的工廠類Factory523.javaFactory320.java中的代碼(這裏以Factory523.java爲例):

public class Factory523 implements Factory {
 
      @Override
      public BMW createBMW() {
           return new BMW523();
      }
 
      @Override
      public AC createAC() {
           return new AC523();
      }
}
測試類Test.java中的代碼:

public class Test {
      private static BMW bmw;
      private static AC ac;
 
      private static final String[] classes = {
                 "com.itgungnir.designpattern.factory.abstractfactory.Factory523",
                 "com.itgungnir.designpattern.factory.abstractfactory.Factory320" };
 
      public static void main(String[] args) {
           Scannerscanner = new Scanner(System.in);
           System.out.println("請選擇要生產的產品的型號:");
           System.out.println("1、523型號");
           System.out.println("2、320型號");
           int type = scanner.nextInt();
 
           try {
                 Class<?>factory = Class.forName(classes[type - 1]);
                 MethodcreateBMW = factory.getMethod("createBMW");
                 MethodcreateAC = factory.getMethod("createAC");
                 bmw = (BMW) createBMW.invoke(factory.newInstance());
                 ac = (AC) createAC.invoke(factory.newInstance());
           }catch (ClassNotFoundException e) {
                 e.printStackTrace();
           }catch (NoSuchMethodException e) {
                 e.printStackTrace();
           }catch (SecurityException e) {
                 e.printStackTrace();
           }catch (IllegalAccessException e) {
                 e.printStackTrace();
           }catch (IllegalArgumentException e) {
                 e.printStackTrace();
           }catch (InvocationTargetException e) {
                 e.printStackTrace();
           }catch (InstantiationException e) {
                 e.printStackTrace();
           }
 
           if (bmw != null && ac!=null) {
                 bmw.introduce();
                 ac.introduce();
           }else {
                 System.out.println("沒有這個型號的產品!");
           }
 
           scanner.close();
      }
}
運行結果如下圖所示:

5      後記

這個帖子中的所有例子都很簡單,都是直接創建的,這樣還不能把工廠模式淋漓盡致的體現出來。設想,如果一個產品的創建需要很多步驟,中間會產生很多很多的對象,而這些對象最終都沒有返回(即這些對象對我們最終想要獲得的對象只是輔助作用,我們不需要獲取這些對象)。在這種情況下,我們如果每獲取一個對象都調用這一套方法的話無疑是非常耗時耗力的。

 

因此,工廠模式不僅對“new”進行了封裝,也在一定程度上對對象的創建過程進行了封裝。使用工廠模式,可以得到更松耦合、更有彈性的設計。

 

最後總結一下工廠模式不同類別的定義:

l  工廠方法模式:定義一個創建對象的接口或抽象類,由實現類或子類決定要實例化的類是哪一個。工廠方法模式讓類把實例化的代碼推遲到子類中進行;

l  抽象工廠模式:提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類。

 

最後貼出工廠模式的GitHub地址:【GitHub - Factory】


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章