設計模式之【工廠模式】
以心法為基礎,以武器運用招式應對複雜的編程問題。
表妹:哥啊,我今天看新聞說,歐盟擴大對俄羅斯軍工企業及部門的制裁。
我:是啊,俄烏局勢這麼緊張,歐美國家通過這種手段,試圖削弱俄羅斯的戰鬥力。我們知道,軍工廠是輸出武器裝備的…
現實生活中,有很多工廠,有生產武器裝備的,叫做「軍工廠」;有生產化學藥品的,叫做「化工廠」;還有「晶片工廠」等等。
在我們軟體開發中,也有「工廠」這麼一說。
什麼叫「工廠」?
工廠顧名思義,就是創建產品。該模式封裝和管理對象的創建,通俗地講就是,你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 }
可以通過下圖,更直觀的認識簡單工廠模式。
簡單工廠模式的特點
-
它是一個具體的類,非介面,非抽象類。有一個重要的make()方法,利用if或者switch分支創建不同的武器並返回。
-
make()方法通常是靜態的,所以也稱為靜態工廠。
簡單工廠模式的缺點
-
違背了設計原則之【開放封閉原則】。假如現在前線需要飛機支援,除了新增一個飛機武器類(擴展),還需要修改工廠類方法(修改)。
-
不同的武器需要不同的額外參數的時候,是不支援的,導致不夠靈活。
-
簡單工廠模式由於使用了靜態工廠方法,靜態方法不能被繼承和重寫,會造成工廠角色無法形成基於繼承的等級結構。
為什麼使用工廠模式?
在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 }
你看,這樣就不用通過指定類型來創建對象了。
工廠模式的優點
-
更符合開-閉原則。新增一種武器時,只需要增加相應的具體武器類和相應的軍工廠子類(生產車間)即可。而簡單共產模式需要修改工廠類的判斷邏輯。
-
符合單一職責原則。每個具體工廠類只負責創建對應的武器。而簡單工廠中的軍工廠類存在複雜的if或者switch邏輯判斷。
-
不使用靜態工廠方法,可以形成基於繼承的等級結構。而簡單工廠模式的軍工廠類使用靜態工廠方法。
工廠模式的缺點
-
添加新的武器時,除了新增新武器類外,還需要提供與之對應的具體工廠類(生產車間),系統類的個數將成對增加,在一定程度上增加了系統的複雜度;同時,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
-
由於考慮到系統的可擴展性,需要引入抽象層,在客戶端程式碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能存在用到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 }
現在,你還敢相信我槍里沒有子彈了嘛?
抽象工廠模式的優點
-
抽象工廠模式隔離了具體類的生產,使得客戶並不需要知道什麼被創建。
-
當一個產品族中的多個對象被設計成一起工作時,它能保證客戶端始終只使用同一個產品族中的對象。
-
增加新的具體工廠和產品族很方便,無需修改已有程式碼,符合開閉原則。
抽象工廠模式的缺點
增加新的產品等級結構會很複雜,需要修改抽象工廠和所有的具體工廠類。
抽象工廠模式的應用場景
當需要創建的對象是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式。
意思就是,一個繼承體系中,如果存在著多個等級結構(即存在著多個抽象類),並且分屬各個等級結構中的實現類之間存在著一定的關聯或約束,就可以使用抽象工廠模式。假如各個等級結構中的實現類之間不存在關聯或約束,則使用多個獨立的工廠來對產品進行創建,則更適合一點。
總結
設計模式沒有對錯,關鍵看你怎麼用。
參考
//www.cnblogs.com/yssjun/p/11102162.html
//www.cnblogs.com/toutou/p/4899388.html#_label3
//blog.csdn.net/qq564425/article/details/81082242
《設計模式之禪》