第21次文章:工廠模式

  • 2019 年 10 月 8 日
  • 筆記

在我們GOFO23種設計模式中,上期着重介紹了創造型模式中的單例模式,這周我們着重介紹另一種創造型設計模式——工廠模式。

一、工廠模式

-實現了創建者和調用者的分離。

1、詳細分類

-簡單工廠模式

-工廠方法模式

-抽象工廠模式

2、面向對象設計的基本原則

(1)OCP(開閉原則,Open-Closed Principle)

一個軟件的實體,在後期的升級改造過程中,我們不應該通過修改其源代碼來完成我們的目的,而是通過在源代碼的基礎上增加新的模塊來完成我們需要的功能。

(2)DIP(依賴倒轉原則,Dependence Inversion Principle)

在完成每一個類的功能時,我們最好是通過實現接口的方式來實現每一個功能模塊的設計,也就是針對接口編程,不要針對實現編程。具體的實現類可以任意的被修改,如果針對具體是實現來編程,那麼很容易牽一髮而動全身,使得不同類之間耦合性太強。

(3)LoD(迪米特法則,Law of Demeter)

只與你直接的朋友通信,而避免和陌生人通信。這也屬於類與類之間的解耦性,其目的還是高內聚低耦合。

二、簡單工廠模式

1、要點

-簡單工廠模式也被稱為靜態工廠模式,就是工廠類一般是使用靜態方法,通過接受的參數的不同來返回不同的對象實例。

-對於增加新產品無能為力不修改代碼的話,是無法擴展的。

2、利用簡單工廠模式進行簡單的實現

為了模擬簡單工廠的設計模式,我們先講述一個生活中的簡單背景吧。比如現在建造汽車,對於一個用戶而言,並不需要知道建造汽車的每一個細節,僅僅需要知道自己需要什麼車就好了,比如「比亞迪」,「奧迪」等等。所以我們根據這種簡單工廠的設計模式,就可以將其進行實例化。具體如下所示:

(1)因為需要依據接口進行編程,所以先建立一個汽車的接口Car

public interface Car {  void run();}

接口中的run方法,在可以根據後面的需要進行具體實現。

(2)依據我們的簡單假設,設計兩款車——比亞迪和奧迪。所以兩種不同的類就又產生了

public class Audi implements Car{  @Override  public void run() {    System.out.println("奧迪跑!");  }}
public class Byd implements Car{  @Override  public void run() {    System.out.println("比亞迪跑!");    }}

(3)假如我們不通過工廠模式進行建造這兩類車,那麼可以直接使用new方法進行創建

  public static void main(String[] args) {    Audi car1 = new Audi();    Byd car2 = new Byd();        car1.run();    car2.run();  }

tips:

這種情況屬於沒有使用工廠模式時的建造方法,用戶需要自己去構建相應的汽車,反映在代碼裏面的就是使用new方法來構建。這如果對比在現實生活中,就類似於用戶需要知道如何去構建一輛汽車。

(4)建造工廠類,通過工廠類實現用戶的需求

public class CarFactory {   public static Car createCar(String type) {    if("奧迪".equals(type)) {      return new Audi();    }else if("比亞迪".equals(type)) {      return new Byd();    }else {      return null;    }  } }

tips:

在這裡我們將每一輛車的具體實現交給了一個工廠類——CarFactory,通過代碼我們也可以看到,對於不同的汽車,我們的工廠通過傳輸進來的參數進行區分。

(5)利用簡單工廠模式,我們再來觀察一下建造一個汽車的流程。

  public static void main(String[] args) {    Car car1 =  CarFactory.createCar("奧迪");    Car car2 =  CarFactory.createCar("比亞迪");        car1.run();    car2.run();  }

tips:

1:與普通模式(3)相比,在簡單工廠模式中,我們只需要調用工廠類CarFactory,並調用其中的建造方法——createCar,並傳遞用戶需要的參數就好了。對用戶而言,並不需要知道構造一輛汽車的具體方法和步驟。

2:雖然在(3)和(5)中,用戶需要構建一輛汽車的代碼數量並沒有減少,但是這僅僅是因為我們涉及的實例背景是十分簡單的一個例子。對比兩者的思想,普通模式(3)更加要求用戶對創建汽車細節的掌握,而簡單工廠模式(5),主要是將整個創建汽車的細節全部封裝在工廠中。假如遇到一個更加複雜的工藝流程,可以明顯的感受到簡單工廠模式的便利之處。

3:但是簡單工廠也有一個很明顯的缺點,就是不滿足我們之前提到的OCP開閉原則。假如我們需要新增一款汽車的建造方法,比如說「奔馳」,那麼我們就需要在CarFactory中新增「type」類型識別,以及相應的創建流程。而這些改動,都是在源代碼塊CarFactory中進行實現的,而不是直接進行擴展所得。

三、工廠方法模式

1、要點

-為了避免簡單工廠模式的缺點,不完全滿足OCP。

-工廠方法模式和簡單工廠模式最大的不同在於,簡單工廠模式只有一個(對於一個項目或者一個獨立模塊而言)工廠類,而工廠方法模式有一組實現了相同接口的工廠類。工廠方法模式屬於簡單工廠模式的一個升級版。

2、模式實現

在前面的簡單工廠模式中,不滿足OCP原則的核心在於,我們將CarFactory設計成為了一個「混亂」的類。每一種汽車的構建生產,我們都將其構造方法放入到CarFactory中,這樣就會導致所有不同汽車的構造都依賴於這一個類中進行實現,所以新增汽車的構造也需要在CarFactory中進行實現,這樣就大大的提升了每個類的耦合性,使得工廠中的功能太過於雜亂。所以針對這個問題,就有相應的工廠方法模式對其進行解決。工廠方法模式直接在簡單工廠模式的基礎上,更加細分CarFactory的每一種功能,將CarFactory也定義為一個接口,然後每一種不同的汽車,單獨創建一個工廠,並且實現CarFactory接口的功能。這樣使用一組單一汽車的製造工廠來代替一個混合汽車製造工廠。下面來具體的說明其中的改變。

(1)將CarFactory從實現類改為一個接口

public interface CarFactory {  Car createCar();}

(2)針對每一款汽車,構造不一樣的製造工廠

public class AudiCarFactory implements CarFactory {  @Override  public Car createCar() {    return new Audi();  }}
public class BydCarFactory implements CarFactory{  @Override  public Car createCar()     return new Byd();  }}

(3)此時我們在構建「比亞迪」和「奧迪」的時候,程序就成為下面這樣

  public static void main(String[] args) {    Car c1 = new AudiCarFactory().createCar();    Car c2 = new BydCarFactory().createCar();        c1.run();    c2.run();  }

tips:

1:在簡單工廠中,我們使用的createCar是CarFactory中的靜態方法,所以不需要new一個CarFactory對象,可以直接調用此方法。然而在工廠方法模式中,CarFactory是一個接口,其他的汽車構建類都是實現該接口來重寫創建方法,所以每種不同汽車的createCar方法都無法設置為靜態方法,在調用的時候需要依賴於一個實現類,所以需要new每一個汽車構建工廠類。

2:與此同時,如果我們現在需要新增一輛「奔馳」車的構建方式,那麼我們的做法也很簡單。在不改動源代碼的情況下,我們分別增加一個「奔馳」車類,和一個「奔馳」車的建造工廠就可以直接實現新增車輛的生產了。實現如下:

public class Benz implements Car {  @Override  public void run() {    System.out.println("奔馳在跑");  }}
public class BenzCarFactory implements CarFactory{  @Override  public Car createCar() {    return new Benz();  }}

此時,用戶端的操作為

Car c3 = new BenzCarFactory().createCar();

通過這種方式,也就直接對生產工廠與不同的車種進行區分,完美的實現了OCP原則。

四、簡單工廠模式和工廠方法模式對比:

1、結構複雜度

簡單工廠模式佔優。簡單工廠模式只需要一個工廠類,而工廠模式的工廠類隨着產品類個數的增加而增加,這無疑會使類的個數越來越多,從而增加了結構的複雜程度。

2、代碼複雜度

簡單工廠模式在結構方面相對簡潔,在代碼方面就會比工廠方法模式複雜。簡單工廠模式的工廠類隨着產品類的增加需要增加很多方法(或代碼),而工廠方法模式每個具體工廠類只完成單一任務,代碼簡潔。

3、客戶端編程難度

工廠方法模式對染在工廠結構中引入了接口從而滿足了OCP ,但是在客戶端編碼中需要對工廠類進行實例化,而簡單工廠模式的工廠類是個靜態類,在客戶端無需實例化。

4、實際選擇

根據設計理論建議,我們應該選擇工廠方法模式,但是在實際上,我們一般都用簡單工廠模式。其實也很好理解,就好比通信中的七層網絡結構屬於理論中最優的,然而在實際的使用中,都是用的是TCP/IP四層結構,其實歸根到底,理論上的最優都是需要付出一定的代價,比如代碼的複雜程度,建設成本的增加等等,這些在實際的使用中都需要加以考慮。而在此處,我們通過對比也可以明顯的感覺到簡單工廠模式的簡潔方便之處,所以一般選擇簡單工廠模式。

五、抽象工廠模式

1、要點

-用來生產不同產品族的全部產品。(對於增加新的產品,無能為力;支持增加產品族)

-抽象工廠模式在有多個業務品種、業務分類時,通過抽象工廠模式產生需要的對象是一種非常好的解決方式。

2、具體實現

其實抽象工廠模式也是很具體的一個例子。以我們上面的建造汽車的過程而言,在我們現實生活中,我們可以粗略的將其分為低端汽車和高端汽車,每種汽車對應的發動機、座椅、輪胎等等都可以加以區分高端和低端。我們設置為高端汽車使用高端的部件,而低端汽車使用低端的部件。然後就可以通過每種汽車整體,來獲取其中的相關部件。例如:

(1)首先規定發動機、座椅、輪胎均有高端和低端兩種質量

public interface Engine {  void run();  void start();}  class LuxuryEngine implements Engine{  @Override  public void run() {    System.out.println("高端發動機轉的快!");  }  @Override  public void start() {    System.out.println("高端發動機可以自啟動!");  }}  class LowEngine implements Engine{  @Override  public void run() {    System.out.println("低端發動機轉的慢!");  }  @Override  public void start() {    System.out.println("低端發動機不可以自啟動!");  }}

這裡僅僅將發動機的構建類代碼放在這裡,座椅和輪胎的構建與其相同,都是先定義一個座椅或者輪胎接口,然後再具體的實現高端和低端產品類。

(2)再建立一個相應汽車構建工廠接口

public interface CarFactory {  Engine createEngine();  Seat createSeat();  Tyre createTyre();}

分別實現高端汽車和低端汽車的構建

public class LuxuryCarFactory implements CarFactory {  @Override  public Engine createEngine() {    return new LuxuryEngine();  }    @Override  public Seat createSeat() {    return new LuxurySeat();  }  @Override  public Tyre createTyre() {    return new LuxuryTyre();  }}

這裡僅列舉出高端汽車的構建工廠,低端汽車構建工廠與之類似。

(3)我們在用戶端進行測試一下

public class Client {  public static void main(String[] args) {    CarFactory c1 = new LuxuryCarFactory();    Engine e1 = c1.createEngine();    Seat s1 = c1.createSeat();    Tyre t1 = c1.createTyre();      e1.run();    e1.start();    s1.massage();    t1.revolve();        System.out.println("####################");        CarFactory c2 = new LowCarFactory();    Engine e2 = c2.createEngine();    Seat s2 = c2.createSeat();    Tyre t2 = c2.createTyre();      e2.run();    e2.start();    s2.massage();    t2.revolve();  }}

檢查一下結果

tips:

如上所示,我們通過高端汽車工廠產生一個高端汽車對象,然後可以通過這個對象獲取一整個產品族。這就是我們抽象工廠模式的主要思想,這樣可以幫助在我們生產一系列雜亂的對象的時候,有一個更加清晰的思路,也便於我們操作。

六、工廠模式要點

-簡單工廠模式:雖然某種程度不符合設計原則,但實際使用最多。

-工廠方法模式:不修改已有類的前提下,通過增加新的工廠類實現擴展。

-抽象工廠模式:不可以增加產品,可以增加產品族!

七、對於工廠模式的一點理解

通過介紹工廠模式,其實仔細體會其中的邏輯,也就是我們社會進化的一種內在邏輯。

在最初的農耕社會,我們需要自己去了解如何種地,播種,收割,將農作物轉化為麵粉等等流程,每個人的知識面很寬廣,但是並不利於效率的提升。而在我們現在的社會中,生產方式被不斷的改進,種植人員僅需要知道種植,不負責後面的任何其他事情。這也就體現了社會分工明確的一種優勢,每個人知道的很少,但卻很專註其中某一項,這樣可以大大的提升效率。

就好比剛剛的簡單工廠模式,用戶在生產汽車的時候,甚至可以不知道new方法,只要知道createCar方法就夠了,在createCar方法背後發生了什麼,並不屬於用戶需要考慮的問題。在後面的工廠方法模式中,同樣是對耦合性的降低,只要做到少依賴同一個類,這樣的系統才會更加穩定牢固,這種思想又恰恰是現在社會進步的一種模式,世界的多極化背後,也同樣是基於這樣一個理論。所以設計模式的學習,更重要的應該是去理解模式背後的邏輯是什麼,應用的方面也就不止於編程上了!