文章首發於「陳樹義」公眾號及個人博客 shuyi.tech,歡迎訪問更多有趣有價值的文章。
文章首發於「陳樹義」公眾號及個人博客 shuyi.tech,歡迎訪問更多有趣有價值的文章。
工廠模式是編程中用得最多的設計模式。本文由一個簡單的生活例子觸發,從工廠方法模式到簡單工廠模式,再到多工廠模式,最后到抽象工廠模式。環環相扣,深入淺出,讓讀者看着大呼過癮!
小黑眼瞅着年近35歲,於是想着搞搞副業預防中年危機。有一天走在路上,看到路邊一家炸雞店在賣薯條。善於思考的小黑想着:我是否能將這個過程用面向對象的語言表達出來?於是小黑在腦海中開始構思。
// 薯條接口
public interface Chips {
void peel();
void cut();
void fried();
}
// 普通薯條
public class CommonChips implements Chips {
@Override
public void peel() {
System.out.println("土豆去皮");
}
@Override
public void cut() {
System.out.println("土豆切條");
}
@Override
public void fried() {
System.out.println("炸薯條");
}
}
那當客人點了薯條之后,炸雞店應該怎么做一份薯條呢?小黑很快地在腦海中寫下了下面的代碼。
public class Client {
public static void main(String[] args) {
// 店員開始炸薯條啦~
Chips chips = new CommonChips();
chips.peel();
chips.cut();
chips.fried();
}
}
不出意外,最終結果應該是下面這樣。小黑說道。
土豆去皮
土豆切條
炸薯條
過了幾天,小黑看到肯德基推出了波紋薯條,於是炸雞店也跟進推出新品。於是炸雞店的店員也不得不跟着改進下切薯條的方法。小黑想:這種情況,我們的類結構應該怎么調整呢?想了一會之后,小黑說:我們只需要在 Chips 薯條類增加一個新的切波紋薯條方法,之后讓店員用新的方法去切薯條即可,其它的步驟都不用變。
// 薯條
public class CommonChips implements Chips {
@Override
public void peel() {
System.out.println("土豆去皮");
}
// 增加一個切波紋薯條的方法
@Override
public void cutBoWen() {
System.out.println("土豆切成波紋");
}
@Override
public void fried() {
System.out.println("炸薯條");
}
}
店員制作薯條的時候的步驟需要變換一下:
public class Client {
public static void main(String[] args) {
// 店員開始炸波紋薯條啦~
Chips chips = new CommonChips();
chips.peel();
chips.cutBoWen();
chips.fried();
}
}
如無意外,結果應該是:
土豆去皮
土豆切成波紋
炸薯條
看起來這樣的操作完全沒問題,問題可以完美解決。不過小黑總覺得哪里不對勁,但又一時沒想到原因。直到他聽到店員吐苦水說:我就賣薯條的,你還要讓我學怎么做薯條,多麻煩啊。還不如直接把薯條做好,我直接炸薯條就行。這樣我就不用關心薯條怎么做的了。 下次做旋風薯條、麻辣薯條等等的時候,我也不用關心薯條怎么做,直接炸薯條就可以了。
工廠方法模式
聽到這里小黑煥然大悟!作為售賣的店員來說,他不需要關注原材料怎么生產的,只需要知道怎么做好賣給顧客就可以了。這不就和我們編程中的工廠方法模式類似么?
工廠方法模式指的是使用者與創建者分離,使用者不需要知道對象是怎么創建出來的,而創建的過程封裝在工廠里。 這就像這家炸雞店一樣,店員(使用者)不需要關心薯條怎么做出來的,薯條怎么做出來交給中央廚房(工廠)去做就可以了。
於是小黑調整了一下炸薯條的實現,使用工廠方法來實現。具體實現上,增加了一個工廠類來制作薯條。
文章首發於「陳樹義」公眾號及個人博客 shuyi.tech,歡迎訪問更多有趣有價值的文章。
// 工廠抽象類
public abstract class AbstractFoodFactory {
public abstract Chips make(String type);
}
// 具體工廠類
public class ChipFactory extends AbstractFoodFactory{
@Override
public Chips make(String type) {
if (type.equals("common")) {
Chips chips = new CommonChips();
chips.peel();
chips.cut();
return chips;
} else if (type.equals("bowen")) {
Chips chips = new CommonChips();
chips.peel();
chips.cutBoWen();
return chips;
}
return null;
}
}
此時店員怎么賣薯條呢?直接去工廠拿到薯條,之后炸薯條就可以了!
public class Client {
public static void main(String[] args) {
// 直接告訴工廠要什么薯條
FoodFactory foodFactory = new FoodFactory();
Chips chips = foodFactory.make("bowen");
// 拿到薯條后直接炸薯條
chips.fried();
}
}
想到這里,小黑不由得感嘆:編程其實就是現實世界的投射。工廠方法模式,本質上就是將對象的實例化與使用分離開來,這使得使用方不需要去關心對象的實例化。要解決的問題是:希望能夠創建一個對象,但創建過程比較復雜,希望對外隱藏這些細節。 在這個例子中,就是店員不需要去關心薯條怎么做出來的,只需要直接炸薯條就可以了。這樣就可以炸更多薯條,掙更多錢了!
簡單工廠模式
但小黑還是覺得工廠方法模式太復雜了。你看 AbstractFoodFactory 類其實只有一個實現類,那這種情況下沒必要還弄一個抽象類,還弄個實現類,這樣多累啊。直接弄一個提供靜態方法的工廠類不就好了。於是小黑調整了一下代碼。
// 創建簡單工廠類
public class SimpleFoodFactory {
// 提供靜態方法
public static Chips make(String type) {
if (type.equals("common")) {
Chips chips = new CommonChips();
chips.peel();
chips.cut();
return chips;
} else if (type.equals("bowen")) {
Chips chips = new CommonChips();
chips.peel();
chips.cutBoWen();
return chips;
}
return null;
}
}
// 店員炸雞更快了!
public class Client {
public static void main(String[] args) {
// 不用創建食物工廠了,直接拿薯條!
Chips chips = SimpleFoodFactory.make("bowen");
chips.fried();
}
}
可以看到整個類結構精簡了,不需要抽象工廠類。而在使用的時候,店員也可以直接拿到薯條,不需要去創建食物工廠了!
其實這就是我們常說的簡單工廠模式!在只有一個工廠實現的時候,我們可以簡化成提供靜態方法的簡單工廠類,從而簡化使用。
多工廠模式
除了簡單工廠類,我們還有多工廠模式,小黑說道。
多工廠模式就是每種類型的產品單獨作為一個工廠,例如:普通薯條單獨作為一個工廠,波紋薯條單獨作為一個工廠。為什么要這么做呢?這是因為在單種對象初始化比較復雜的時候,所有產品類的初始化都放到一個類中,會使得代碼結構不清晰,這時候就用多工廠模式。 例如我們的波紋薯條非常復雜,可能需要 100 道工序,那和普通薯條放在同一個工廠制作就不太合適,於是我們單獨建了一個制作波紋薯條的工廠。
於是小黑繼續對之前的代碼做改造。Chips 類和 Food 接口還是沒有變動,有變化的僅僅是工廠類。
// 新的抽象工廠類
public abstract class AbstractFoodFactory {
// 不需要告訴我要什么類型的薯條了,一個工廠只做一種薯條
public abstract Chips make();
}
// 普通薯條工廠
public class CommonChipFactory extends AbstractFoodFactory{
@Override
public Chips make() {
Chips chips = new CommonChips();
chips.peel();
chips.cut();
return chips;
}
}
// 波紋薯條工廠
public class BowenChipFactory extends AbstractFoodFactory{
@Override
public Chips make() {
Chips chips = new CommonChips();
chips.peel();
chips.cutBoWen();
return chips;
}
}
現在店員炸薯條變成了這樣:
// 店員炸雞更快了!
public class Client {
public static void main(String[] args) {
// 去普通薯條工廠直接拿薯條
CommonChipFactory commonChipFactory = new CommonChipFactory();
Chips chips = commonChipFactory.make();
chips.fried();
// 去波紋薯條工廠拿波紋薯條
BowenChipFactory bowenChipFactory = new BowenChipFactory();
chips = bowenChipFactory.make();
chips.fried();
}
}
抽象工廠模式
看到多工廠模式,大家是不是已經累趴了,但其實還有抽象工廠模式!
抽象工廠模式,其實就是工廠模式更高級的抽象。從名字可以知道,抽象二字是用來形容工廠的,那說明在抽象工廠模式中,工廠也被抽象出來了。 例如對於肯德基和麥當勞來說,他們的薯條都是由供應商提供的,那么對於供應商來說,他們如何去表示這個過程呢?
首先,我們先創建一個工廠類,可以做普通薯條和波紋薯條。
public interface ChipFactory {
// 薯條族,即普通薯條,還是波紋薯條
void makeChip();
void makeBowenChip();
}
文章首發於「陳樹義」公眾號及個人博客 shuyi.tech,歡迎訪問更多有趣有價值的文章。
那么肯德基肯定有其對應的薯條工廠,麥當勞也有其薯條工廠。
// 肯德基薯條工廠
public class KfcChipFactory implements ChipFactory{
@Override
public void makeChip() {
System.out.println("生產肯德基普通薯條");
}
@Override
public void makeBowenChip() {
System.out.println("生產肯德基波紋薯條");
}
}
// 麥當勞薯條工廠
public class MacDonaldChipFactory implements ChipFactory{
@Override
public void makeChip() {
System.out.println("生產麥當勞普通薯條");
}
@Override
public void makeBowenChip() {
System.out.println("生產麥當勞波紋薯條");
}
}
最后我們用一個場景類來表示生產過程。
public class Client {
public static void main(String[] args) {
ChipFactory kfcChipFactory = new KfcChipFactory();
kfcChipFactory.makeChip();
kfcChipFactory.makeBowenChip();
ChipFactory macChipFactory = new MacDonaldChipFactory();
macChipFactory.makeChip();
macChipFactory.makeBowenChip();
}
}
可以看到,抽象工廠比起工廠方法,最大的區別在於:抽象工廠是兩層的抽象結構,而工廠方法則只有一層抽象。這就使得抽象工廠能夠表示更多的內容,而工廠方法表達的內容更少。 在這個例子中,工廠方法模式表示的是薯條類型,而表示不了肯德基、麥當勞的品牌類型。而抽象工廠不僅可以表示薯條類型,也可以表示品牌類型。
但其實抽象工廠也有一些壞處,例如當我們要增加一種新的薯條類型時,我們需要修改 ChipFactory 工廠類,又要修改每個實現類。這就違背了我們的開閉原則,使得代碼非常不穩定。但抽象工廠也有好處,即當我們有一個新的品牌時,擴展非常方便。例如當我們有德克士這個品牌時,我們可以直接增加一個 DicosChipFactory 類,實現 ChipFactory 接口就可以了。
這要求我們要用變化的角度去分析需求,看看哪些是變化更大的,將變化的東西使用類結構來承載。例如在生產薯條的例子中,生產新薯條的場景相對較少、新品牌可能變動,那么我們就應該將薯條類型作為產品族,這樣變化就不大。
總的來說,抽象工廠一般用在多個維度,即有產品族的情況下。產品族作為用第一層的抽象類來承載,但如果產品族變化很大則不適合使用抽象工廠。
總結
想到這里,小黑感覺知識間好像都關聯起來了。
- 工廠方法是用來分類使用與創建的,創建對象使用工廠方法實現,創建的過程封裝在工廠類的方法中,我們不需要關心對象是怎么生產的。
- 如果工廠方法只創建一種類型的對象,那么可以將工廠類簡化成帶靜態方法的工廠類,去掉工廠抽象類,減少類結構冗余。
- 如果工廠方法要創建很多種類型的對象,而每種對象的創建過程都很復雜,那么就用多工種模式,即每種產品都對應一個工廠類,這就形成了多工廠模式。
- 如果產品有多個產品族(兩個維度的變量),那么可以進一步抽象成抽象工廠模式。
總的來說,就是以工廠方法為基點,往前縮變成了簡單工廠,往后擴展變成了多工廠,往上一層就變成了抽象工廠。
注:在設計模式中,其實只有工廠方法模式和抽象工廠模式兩種。簡單工廠模式、多工廠模式、普通工廠方法,都屬於工廠方法。