設計模式之【工廠模式】

Gopher大威發表於2022-03-03

設計原則是指導我們程式碼設計的一些經驗總結,也就是“心法”;物件導向就是我們的“武器”;設計模式就是“招式”。

以心法為基礎,以武器運用招式應對複雜的程式設計問題。

表妹:哥啊,我今天看新聞說,歐盟擴大對俄羅斯軍工企業及部門的制裁。

我:是啊,俄烏局勢這麼緊張,歐美國家通過這種手段,試圖削弱俄羅斯的戰鬥力。我們知道,軍工廠是輸出武器裝備的...

現實生活中,有很多工廠,有生產武器裝備的,叫做“軍工廠”;有生產化學藥品的,叫做“化工廠”;還有“晶片工廠”等等。

在我們軟體開發中,也有“工廠”這麼一說。

什麼叫“工廠”?

工廠顧名思義,就是建立產品。該模式封裝和管理物件的建立,通俗地講就是,你new一個物件的時候,直接呼叫工廠方法就行了。

簡單工廠模式

簡單工廠模式就是把對類的建立初始化全都交給一個工廠來執行,而使用者不需要關心建立的過程是什麼樣的,只需要告訴工廠,我想要什麼就行了。

 

我們以“俄烏戰爭”為背景,以“軍工廠”為例子。我們定義一個武器IArms介面,也就是產品的標準規範。因為戰場上需要用到很多型別的武器,這裡我們就舉兩種例子,槍和坦克,也就是兩種不同的產品。

 1 public interface IArms {
 2     public void attack() 
 3 }
 4  5 public class Gun implements IArms {
 6     @Override
 7     public void attack() {
 8         System.out.println("我是一支槍");
 9     }
10 }
11 12 public class Tank implements IArms {
13     @Override
14     public void attack() {
15         System.out.println("我是一輛坦克");
16     }
17 }

現在,我們想要生產產品,就需要有工廠。下面就是“軍工廠”的程式碼實現:

 1 public class MilitaryFactory {
 2     public IArms make(String type) {
 3         if (type.equalsIgnoreCase("Gun")) {
 4             return new Gun();
 5         } else if (type.equalsIgnoreCase("Tank")) {
 6             return new Tank();
 7         }
 8         return null;
 9     }
10 }

這個“軍工廠”根據前方的訂單,建立不同的武器。

接下來,我們看一下客戶端如何使用:

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 聯絡到軍工廠的廠長
 4         MilitaryFactory mf = new MilitaryFactory();
 5         // 現在前線需要槍支,下Gun訂單
 6         IArms gun = mf.make("Gun");
 7         // 戰爭激烈,需要坦克支援,下Tank訂單
 8         IArms tank = mf.make("Tank");
 9         
10         ... // 省去武器運往前線的過程 
11         
12         // 前線拿到武器之後,加入戰鬥
13         gun.attack();
14         tank.attack();
15     }
16 }

可以通過下圖,更直觀的認識簡單工廠模式。

 

簡單工廠模式的特點

  1. 它是一個具體的類,非介面,非抽象類。有一個重要的make()方法,利用if或者switch分支建立不同的武器並返回。

  2. make()方法通常是靜態的,所以也稱為靜態工廠

簡單工廠模式的缺點

  1. 違背了設計原則之【開放封閉原則】。假如現在前線需要飛機支援,除了新增一個飛機武器類(擴充套件),還需要修改工廠類方法(修改)。

  2. 不同的武器需要不同的額外引數的時候,是不支援的,導致不夠靈活。

  3. 簡單工廠模式由於使用了靜態工廠方法,靜態方法不能被繼承和重寫,會造成工廠角色無法形成基於繼承的等級結構。

為什麼使用工廠模式?

在OO設計中,有一個重要的設計原則,就是針對介面而非實現程式設計。每當我們使用new去例項化一個物件時,用到的就是實現程式設計,而不是介面。這樣一來,程式碼繫結著具體類,會導致程式碼更脆弱,缺乏彈性。

工廠方法模式,定義了一個建立物件的介面,但由子類(具體工廠)決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類

下圖可以很明顯的表達這個意思。

 

那麼,武器類的結構體系保持不變,主要是將“軍工廠”抽象化,其定義了武器的生產介面,但不負責具體的武器生產,將生產任務交給不同的派生類工廠,與其說派生類工廠,不如說是這個軍工廠裡面的車間,每個車間負責專門生產一種武器。

 1 public interface IArms {
 2     public void attack()
 3 }
 4  5 public class Gun implements IArms {
 6     @Override
 7     void attack() {
 8         System.out.println("我是一支槍");
 9     }
10 }
11 12 public class Tank implements IArms {
13     @Override 
14     void attack() {
15         System.out.println("我是一輛坦克");
16     }
17 }
18 // 以上程式碼保持不變
19 20 // 生產不同武器的軍工廠介面
21 public interface MilitaryFactory {
22     public IArms make()
23 }
24 25 // 生產槍的車間
26 public class GunFactory implements MilitaryFactory {
27     @Override 
28     public IArms make() {
29         return new Gun();
30     }
31 }
32 33 // 生產坦克的車間
34 public class TankFactory implements MilitaryFactory {
35     @Override 
36     public IArms make() {
37         return new Tank();
38     }
39 }
40 41 // 未來,還可以新建一個生產飛機的車間
42 ....

你看,最開始的簡單工廠模式就是將所有的生產任務都放在一個車間裡完成的。這種模式適用於小型的軍工廠,只生產幾種武器,而且未來不會頻繁地新增其他武器,所以,使用一個車間就夠了。

現在的工廠模式,就適用於大型的軍工廠,這個工廠生產很多種武器,而且未來還要研發出新的武器。那麼,使用一個車間就遠遠不夠了,所以,需要很多個車間,且每個車間負責一種武器的生產任務,未來研發出來的新武器,也另外新建一個車間來負責生產,不影響之前的生產車間。

你看,這樣就遵守了設計原則之【開放封閉原則】,使程式碼具有彈性。

那麼,此時客戶端又如何呼叫呢?

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 前線聯絡到軍工廠的槍生產車間的主管
 4         MilitaryFactory gunfactory = new GunFactory();
 5         // 前線聯絡到軍工廠的坦克生產車間的主管
 6         MilitaryFactory tankfactory = new TankFactory();
 7         // 生產車間主管下令具體的生產任務
 8         IArms gun = gunfactory.make();
 9         IArms tank = tankfactory.make();
10         
11         ... // 省略武器運往前線的過程
12            
13         // 前線拿到武器,加入火力進攻
14         gun.attack();
15         tank.attack();
16     }
17 }

你看,這樣就不用通過指定型別來建立物件了。

工廠模式的優點

  1. 更符合開-閉原則。新增一種武器時,只需要增加相應的具體武器類和相應的軍工廠子類(生產車間)即可。而簡單共產模式需要修改工廠類的判斷邏輯。

  2. 符合單一職責原則。每個具體工廠類只負責建立對應的武器。而簡單工廠中的軍工廠類存在複雜的if或者switch邏輯判斷。

  3. 不使用靜態工廠方法,可以形成基於繼承的等級結構。而簡單工廠模式的軍工廠類使用靜態工廠方法。

工廠模式的缺點

  1. 新增新的武器時,除了新增新武器類外,還需要提供與之對應的具體工廠類(生產車間),系統類的個數將成對增加,在一定程度上增加了系統的複雜度;同時,有更多的類需要編譯和執行,會給系統帶來一些額外的開銷。

  2. 由於考慮到系統的可擴充套件性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能存在用到DOM、反射等技術,增加了系統的實現難度。

簡單工廠模式和工廠模式的應用場景

以上兩種模式,都各有優缺點,那麼,我們來看看它們各自的應用場景。

簡單工廠模式適用於業務簡單的情況下,或者具體產品很少增加的情況,相比於工廠模式,使用簡單工廠模式更具有可讀性。

當需要做到靈活、可擴充套件的時候,就考慮使用工廠模式。比如需要設計一個連線郵件伺服器的框架,有三種網路協議可供選擇:POP3、IMAP、HTTP,我們就可以把這三種連線方法作為產品類,定義一個介面如IConnectMail,然後定義對郵件的操作方法,用不同的方法實現三個具體的產品類(也就是連線方式)。後面如果支援新的網路協議,那麼就能夠在不違背開閉原則的前提下,做到完美的擴充套件。

抽象工廠模式

 

使用上面兩種模式,前方拿到武器後發現,沒有彈藥,那這仗沒法打啊。難道是因為當時我下單的時候,沒有說清楚要槍支和子彈,坦克和炮彈嗎?

這樣就太麻煩了,打仗打的,飯都沒時間吃了。

這個時候,抽象工廠模式就派上用場啦。

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

那麼,抽象工廠模式和工廠模式有什麼區別呢?

抽象工廠模式是工廠模式的升級版本,他用來建立一組相關或者相互依賴的物件。它與工廠方法模式的區別就在於,工廠方法模式針對的是一個產品等級結構;而抽象工廠模式則是針對多個產品等級結構。

在程式設計中,通常一個產品結構,表現為一個介面或者抽象類,也就是說,工廠方法模式提供的所有產品都是衍生自同一個介面或抽象類,而抽象工廠模式所提供的產品則是衍生自不同的介面或抽象類。

在抽象工廠模式中,有一個產品族的概念:所謂的產品族,是指位於不同產品等級結構功能相關聯的產品組成的家族。抽象工廠模式所提供的一系列產品就組成一個產品族;而工廠方法提供的一系列產品成為一個等級結構。

槍和坦克屬於同一個產品等級結構,而子彈和炮彈屬於另外一個產品等級結構。在這兩個產品等級結構中,槍和子彈是功能相關聯的,坦克和炮彈也是功能相關聯的。所以,抽象工廠應該將這兩個不同的產品等級結構組合在一起,當前線要槍支的時候,才能同時拿到子彈。

如下圖所示:

 

 1 // 武器介面
 2 public interface IArms {
 3     public void attack()   // 武器攻擊
 4 }
 5  6 public class Gun implements IArms {
 7     @Override
 8     void attack() {
 9         System.out.println("我是一支槍");
10     }
11 }
12 13 public class Tank implements IArms {
14     @Override 
15     void attack() {
16         System.out.println("我是一輛坦克");
17     }
18 }
19 20 // 彈藥介面
21 public interface IAmmunition {
22     public void load()  // 彈藥上膛
23 }
24 25 public class Bullet implements IAmmunition {
26     @Override
27     void load() {
28         System.out.println("子彈已上膛,等待開槍");
29     }
30 }
31 32 public class Cannonball implements IAmmunition {
33     @Override 
34     void load() {
35         System.out.println("炮彈已上膛,等待發射");
36     }
37 }
38 39 40 // 生產不同武器的軍工廠介面
41 public interface MilitaryFactory {
42     public IArms makeArm()   // 生產武器
43     public IAmmunition makeAmmunition()  // 生產彈藥
44 }
45 46 // 生產槍和子彈的車間
47 public class GunFactory implements MilitaryFactory {
48     @Override 
49     public IArms makeArm() {
50         return new Gun();
51     }
52     @Override 
53     public IAmmunition makeAmmunition() {
54         return new Bullet();
55     }
56 }
57 58 // 生產坦克和炮彈的車間
59 public class TankFactory implements MilitaryFactory {
60     @Override 
61     public IArms makeArm() {
62         return new Tank();
63     }
64     @Override 
65     public IAmmunition makeAmmunition() {
66         return new Cannonball();
67     }
68 }
69 70 // 未來,還可以新建一個生產飛機和航空彈藥的車間
71 ....

接下來,我們看一下,客戶端如何呼叫:

 1 public class Demo {
 2     public static void main(String[] arg) {
 3         // 前方聯絡到軍工廠生產槍支dan藥車間的主管
 4         MilitaryFactory gunFactory = new GunFactory();
 5         // 前方聯絡到軍工廠生產坦克和炮彈車間的主管
 6         MilitaryFactory tankFactory = new TankFactory();
 7         
 8         //
槍支dan藥
車間主管下令,開足馬力生產槍和子彈  9 IArm gun = gunFactory.makeArm(); 10 IAmmunition bullet = gunFactory.makeAmmunition(); 11 // 坦克和炮彈生產車間主管下令,開足馬力生產坦克和炮彈 12 IArm tank = tankFactory.makeArm(); 13 IAmmunition cannonball = tankFactory.makeAmmunition(); 14 15 ... // 省略武器彈藥運往前線的過程 16 17 // 武器彈藥到達戰場 18 bullet.load(); // 子彈上膛 19 gun.attack(); // 開槍射擊 20 21 cannonball.load(); // 炮彈上膛 22 tank.attack(); // 坦克攻擊 23  } 24 }

現在,你還敢相信我槍裡沒有子彈了嘛?

抽象工廠模式的優點

  1. 抽象工廠模式隔離了具體類的生產,使得客戶並不需要知道什麼被建立。

  2. 當一個產品族中的多個物件被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的物件。

  3. 增加新的具體工廠和產品族很方便,無需修改已有程式碼,符合開閉原則。

抽象工廠模式的缺點

增加新的產品等級結構會很複雜,需要修改抽象工廠和所有的具體工廠類。

抽象工廠模式的應用場景

當需要建立的物件是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式。

意思就是,一個繼承體系中,如果存在著多個等級結構(即存在著多個抽象類),並且分屬各個等級結構中的實現類之間存在著一定的關聯或約束,就可以使用抽象工廠模式。假如各個等級結構中的實現類之間不存在關聯或約束,則使用多個獨立的工廠來對產品進行建立,則更適合一點。

總結

設計模式沒有對錯,關鍵看你怎麼用。

參考

https://www.cnblogs.com/yssjun/p/11102162.html

https://www.cnblogs.com/toutou/p/4899388.html#_label3

https://blog.csdn.net/qq564425/article/details/81082242

《設計模式之禪》

 

相關文章