公號:碼農充電站pro
主頁:https://codeshellme.github.iohtml
工廠模式(Factory Design Pattern)可細分爲三種,分別是簡單工廠,工廠方法和抽象工廠,它們都是爲了更好的建立對象。java
所謂的「工廠」,就是用來將建立對象的代碼封裝起來,由於這部分代碼未來變更的概率很大,因此這裏的「工廠」的實質做用就是「封裝變化」,以便於維護。git
其中用到了「針對接口編程,而非針對實現編程」的設計原則。github
下面經過一個銷售飲料的例子,來對這三種工廠模式進行介紹。shell
假如如今有一位老闆,想開一家飲料店,該店銷售各類口味的飲料,好比蘋果味,香蕉味,橙子味等。編程
將這些飲料用代碼來表示,以下:設計模式
class Drink { public void packing() { // } } class DrinkApple extends Drink { } class DrinkBanana extends Drink { } class DrinkOrange extends Drink { }
Drink
類爲全部其它味道的飲料的父類。app
當有顧客來購買飲料的時候,顧客須要說明想買哪一種口味的飲料,而後服務員就去將該口味的飲料取過來,包裝好,而後交給顧客。框架
下面咱們用最簡單直接的代碼,來模擬飲料店和賣飲料的過程,以下:ide
class DrinkStore { public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; } }
可是這種實現方式有個問題,就是當須要下架舊飲料或上架新飲料的時候,會致使下面這部分代碼被頻繁的修改:
if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); }
那這就違背了設計原則中的開閉原則:代碼應該對擴展開發,對修改關閉。因此咱們須要對該代碼進行改進,那如何修改呢?
簡單工廠模式告訴咱們要將相似這樣的代碼封裝到一個類裏邊,這個類就叫作簡單工廠類,該類中提供一個方法,它能夠生產咱們所須要的各類對象。
下面用代碼來模擬這個簡單工廠類,以下:
class SimpleDrinkFactory { public Drink createDrink(String flavor) { Drink drink; // 這段容易被頻繁修改的代碼,被封裝到了工廠類中 if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } }
能夠看到,createDrink
方法完成了建立 Drink
的任務。
createDrink
方法也能夠定義成靜態方法,優勢是在使用 createDrink
方法時不須要再建立對象,缺點是不能再經過繼承的方式來改變 createDrink
方法的行爲。
下面來看如何使用 SimpleDrinkFactory
類:
class DrinkStore { private SimpleDrinkFactory factory; public DrinkStore(SimpleDrinkFactory factory) { this.factory = factory; } public Drink sellDrink(String flavor) { Drink drink = factory.createDrink(flavor); drink.packing(); return drink; } }
能夠看到,咱們將 SimpleDrinkFactory
類的對象做爲 DrinkStore
類的一個屬性,通過改進後的 sellDrink
方法就不須要再被頻繁修改了。若是再須要上架下架飲料,則去修改簡單工廠類 SimpleDrinkFactory
便可。
我將完整的簡單工廠代碼放在了這裏,供你們參考,類圖以下:
簡單工廠模式從嚴格意義來講並非一個設計模式,而更像一種編程習慣。
工廠方法模式定義了一個建立對象的接口(該接口是一個抽象方法,也叫作「工廠方法」),但由子類(實現抽象方法)決定要實例化的類是哪一個。
在工廠方法模式中,父類並不關心子類的具體實現,可是父類給了子類一個「規範」,讓子類必須「生成」父類想要的東西。
工廠方法將類的實例化推遲到了子類中,讓子類來控制實例化的細節,也就是將建立對象的過程封裝了起來。而真正使用實例的是父類,這樣就將實例的「實現」從「使用」中解耦出來。所以,工廠方法模式比簡單工廠模式更加有「彈性」。
下面咱們來看下如何用工廠方法模式來改進上面的代碼。
首先,定義 DrinkStoreAbstract
,它是一個抽象父類:
abstract class DrinkStoreAbstract { // final 防止子類覆蓋 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用實例 drink.packing(); return drink; } // 子類必須實現 protected abstract Drink factoryMethod(String flavor); }
上面代碼中 factoryMethod
方法就是所謂的工廠方法,它是一個抽象方法,子類必須實現該方法。
factoryMethod
方法負責生成對象,使用對象的是父類,而實際生成對象的則是子類。
接下來定義一個具體的 DrinkStore
類,它是 DrinkStoreAbstract
的子類:
class DrinkStore extends DrinkStoreAbstract { @Override public Drink factoryMethod(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } return drink; } }
能夠看到,子類中的 factoryMethod
方法有了具體的實現。
若是須要上架下架飲料,則去修改子類中的工廠方法 factoryMethod
便可。而 DrinkStoreAbstract
做爲一個「框架」,無須改動。
完整的工廠方法代碼放在了這裏,供你們參考,類圖以下:
圖中的粉色區域是工廠方法模式的重點關注區。
在介紹抽象工廠模式以前,咱們先來看看什麼是依賴倒置原則。
依賴倒置包含了依賴和倒置兩個詞。
咱們先來看看「依賴」,「依賴」是指類與類之間的依賴關係。
在本文剛開始時的 sellDrink
方法是這麼寫的:
public Drink sellDrink(String flavor) { Drink drink; if (flavor.equals("apple")) { drink = new DrinkApple(); } else if (flavor.equals("banana")) { drink = new DrinkBanana(); } else if (flavor.equals("orange")) { drink = new DrinkOrange(); } else { drink = new Drink(); } drink.packing(); return drink; }
sellDrink
方法依賴了三個具體類,若是飲料的味道繼續增長的話,那麼 sellDrink
方法將依賴更多的具體類。
這會致使只要任意一個具體類發生改變,sellDrink
方法就不得不去改變,也就是類 A 須要改變(A 也是一個具體類)。
在上面這個關係圖中,A
稱爲高層組件,各個具體類稱爲低層組件,因此在這幅圖中,高層組件依賴了低層組件。
依賴倒置原則是指「要依賴抽象,而不依賴具體類」。更具體來講就是,高層組件不該該依賴低層組件,高層組件和低層組件都應該依賴抽象類。
那麼怎樣才能達到依賴倒置原則呢?工廠方法就能夠!
在通過工廠方法模式的改造以後,最終的 DrinkStoreAbstract
類中的 sellDrink
方法變成了下面這樣:
public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用實例 drink.packing(); return drink; }
這使得 sellDrink
的所在類再也不依賴於具體類,而依賴於一個抽象方法 factoryMethod
,而 factoryMethod
方法依賴於 Drink
,而後,各個具體類也依賴於 Drink
,Drink
是一個廣義上的「抽象接口」。
這樣,高層組件和低層組件都依賴於 Drink
抽象,關係圖變成了下面這樣:
以前各個具體類的箭頭是向下指的,而如今各個具體類的箭頭是向上指的,箭頭的方向倒了過來,這就是所謂的依賴倒置。
依賴倒置原則使得高層組件和低層組件都依賴於同一個抽象。
那怎樣才能避免違反依賴倒置原則呢?有下面三個指導方針:
固然事情沒有絕對的,上面三個指導方針,是應該儘可能作到,而不是必須作到。
下面來介紹抽象工廠模式。
假如臨近春節,飲料店老闆爲了更好的銷售飲料,準備購買一批禮盒,來包裝飲料。爲了保證禮盒的質量,規定禮盒只能從特定的地方批發,好比北京,上海等。
那咱們就定義一個接口,讓全部的禮盒都派生自這個接口,以下:
interface DrinkBoxFactory { String createBox(); } class BeiJingBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "BeijingBox"; } } class ShangHaiBoxFactory implements DrinkBoxFactory { @Override public String createBox() { return "ShangHaiBox"; } }
下面須要編寫 Drink
類,咱們讓 Drink
類是一個抽象類,以下:
abstract class Drink { String flavor; protected abstract void packing(); }
須要注意的是,在抽象類 Drink
中有一個抽象方法 packing
,Drink
將 packing
的具體實現交給每一個派生類。Drink
類只規定派生類中須要實現一個 packing
,但並不關心它的具體實現。
下面編寫每種口味的飲料,以下:
class DrinkApple extends Drink { DrinkBoxFactory boxFactory; public DrinkApple(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkApple"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkBanana extends Drink { DrinkBoxFactory boxFactory; public DrinkBanana(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkBanana"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } } class DrinkOrange extends Drink { DrinkBoxFactory boxFactory; public DrinkOrange(DrinkBoxFactory boxFactory) { this.boxFactory = boxFactory; this.flavor = "DrinkOrange"; } @Override public void packing() { System.out.println(flavor + boxFactory.createBox()); } }
能夠看到每種口味的飲料中都有一個 boxFactory
對象,在 packing
時,從 boxFactory
中獲取禮盒。
注意 boxFactory
是一個接口類對象,而不是一個具體類對象,所以,boxFactory
的具體對象是由客戶決定的。
而後,DrinkStoreAbstract
仍是沿用工廠方法模式中的定義,以下:
abstract class DrinkStoreAbstract { // final 防止子類覆蓋 public final Drink sellDrink(String flavor) { Drink drink = factoryMethod(flavor); // 使用實例 drink.packing(); return drink; } // 子類必須實現 protected abstract Drink factoryMethod(String flavor); }
而後咱們實現使用北京禮盒包裝的Store 和使用上海禮盒包裝的Store,以下:
class BeijingDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new BeiJingBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } } class ShangHaiDrinkStore extends DrinkStoreAbstract { @Override protected Drink factoryMethod(String flavor) { Drink drink = null; DrinkBoxFactory factory = new ShangHaiBoxFactory(); if (flavor.equals("apple")) { drink = new DrinkApple(factory); } else if (flavor.equals("banana")) { drink = new DrinkBanana(factory); } else if (flavor.equals("orange")) { drink = new DrinkOrange(factory); } return drink; } }
通過這麼一些列的改動,咱們到底作了些什麼呢?事情的原由是老闆須要一些禮盒來包裝飲料,這就是需求增長了。
所以咱們引入了一個抽象工廠,即 DrinkBoxFactory
,這個接口是全部禮盒工廠的父類,它給了全部子類一個「規範」。
抽象工廠模式提供了一個接口,用於建立相關對象的家族。該模式旨在爲客戶提供一個抽象接口(本例中就是 DrinkBoxFactory
接口),從而去建立一些列相關的對象,而不需關心實際生產的具體產品是什麼,這樣作的好處是讓客戶從具體的產品中解耦。
最終,客戶是這樣使用 DrinkStoreAbstract
的,以下:
public class AbstractMethod { public static void main(String[] args) { DrinkStoreAbstract beiStore = new BeijingDrinkStore(); beiStore.sellDrink("apple"); beiStore.sellDrink("banana"); beiStore.sellDrink("orange"); DrinkStoreAbstract shangStore = new ShangHaiDrinkStore(); shangStore.sellDrink("apple"); shangStore.sellDrink("banana"); shangStore.sellDrink("orange"); } }
完整的抽象工廠模式代碼放在了這裏,供你們參考,類圖以下:
從該圖能夠看出,抽象工廠模式比工廠方法模式更復雜了一些,另外仔細觀察它們兩個的類圖,各自所關注的地方(粉紅色區域)也是不同的。
工廠方法與抽象工廠的相同點都是將對象的建立封裝起來。不一樣點是:
工廠模式使用到的設計原則有:
本篇文章介紹了三種工廠模式,工廠模式在實際應用中很是普遍,好比 Java 工具類中的 Calendar 和 DateFormat 等。
(本節完。)
推薦閱讀:
歡迎關注做者公衆號,獲取更多技術乾貨。