公號:碼農充電站pro
主頁:https://codeshellme.github.io
工廠模式(Factory Design Pattern)可細分為三種,分別是簡單工廠,工廠方法和抽象工廠,它們都是為了更好的建立物件。
所謂的“工廠”,就是用來將建立物件的程式碼封裝起來,因為這部分程式碼將來變動的機率很大,所以這裡的“工廠”的實質作用就是“封裝變化”,以便於維護。
其中用到了“針對介面程式設計,而非針對實現程式設計”的設計原則。
下面通過一個銷售飲料的例子,來對這三種工廠模式進行介紹。
1,簡單工廠模式
假如現在有一位老闆,想開一家飲料店,該店銷售各種口味的飲料,比如蘋果味,香蕉味,橙子味等。
將這些飲料用程式碼來表示,如下:
class Drink {
public void packing() {
//
}
}
class DrinkApple extends Drink {
}
class DrinkBanana extends Drink {
}
class DrinkOrange extends Drink {
}
Drink
類為所有其它味道的飲料的父類。
當有顧客來購買飲料的時候,顧客需要說明想買哪種口味的飲料,然後服務員就去將該口味的飲料取過來,包裝好,然後交給顧客。
下面我們用最簡單直接的程式碼,來模擬飲料店和賣飲料的過程,如下:
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
即可。
我將完整的簡單工廠程式碼放在了這裡,供大家參考,類圖如下:
2,工廠方法模式
簡單工廠模式從嚴格意義來說並不是一個設計模式,而更像一種程式設計習慣。
工廠方法模式定義了一個建立物件的介面(該介面是一個抽象方法,也叫做“工廠方法”),但由子類(實現抽象方法)決定要例項化的類是哪個。
在工廠方法模式中,父類並不關心子類的具體實現,但是父類給了子類一個“規範”,讓子類必須“生成”父類想要的東西。
工廠方法將類的例項化推遲到了子類中,讓子類來控制例項化的細節,也就是將建立物件的過程封裝了起來。而真正使用例項的是父類,這樣就將例項的“實現”從“使用”中解耦出來。因此,工廠方法模式比簡單工廠模式更加有“彈性”。
下面我們來看下如何用工廠方法模式來改進上面的程式碼。
首先,定義 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
作為一個“框架”,無須改動。
完整的工廠方法程式碼放在了這裡,供大家參考,類圖如下:
圖中的粉色區域是工廠方法模式的重點關注區。
3,依賴倒置原則
在介紹抽象工廠模式之前,我們先來看看什麼是依賴倒置原則。
依賴倒置包含了依賴和倒置兩個詞。
我們先來看看“依賴”,“依賴”是指類與類之間的依賴關係。
在本文剛開始時的 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
抽象,關係圖變成了下面這樣:
之前各個具體類的箭頭是向下指的,而現在各個具體類的箭頭是向上指的,箭頭的方向倒了過來,這就是所謂的依賴倒置。
依賴倒置原則使得高層元件和低層元件都依賴於同一個抽象。
那怎樣才能避免違反依賴倒置原則呢?有下面三個指導方針:
- 變數不要持有具體類的引用。
- 需要 new 物件的地方,要改為工廠方法。
- 類不要派生自具體類,而要從抽象類或介面派生。
- 派生自具體類,就會依賴具體類。
- 子類不要覆蓋父類中已實現的方法。
- 父類中已實現的方法應該被所有子類共享,而不是覆蓋。
當然事情沒有絕對的,上面三個指導方針,是應該儘量做到,而不是必須做到。
4,抽象工廠模式
下面來介紹抽象工廠模式。
假如臨近春節,飲料店老闆為了更好的銷售飲料,準備購買一批禮盒,來包裝飲料。為了保證禮盒的質量,規定禮盒只能從特定的地方批發,比如北京,上海等。
那我們就定義一個介面,讓所有的禮盒都派生自這個介面,如下:
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");
}
}
完整的抽象工廠模式程式碼放在了這裡,供大家參考,類圖如下:
從該圖可以看出,抽象工廠模式比工廠方法模式更復雜了一些,另外仔細觀察它們兩個的類圖,各自所關注的地方(粉紅色區域)也是不一樣的。
工廠方法與抽象工廠的相同點都是將物件的建立封裝起來。不同點是:
- 工廠方法主要關注將類的例項化推遲到子類中。
- 抽象工廠主要關注建立一系列相關的產品家族。
5,總結
工廠模式使用到的設計原則有:
- 針對介面程式設計,而非針對實現程式設計。
- 開閉原則。
- 依賴倒置原則。
- 封裝變化。
本篇文章介紹了三種工廠模式,工廠模式在實際應用中非常廣泛,比如 Java 工具類中的 Calendar 和 DateFormat 等。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術乾貨。