硬核!史上最全的工廠模式文章,從零到一全面講解!


文章首發於「陳樹義」公眾號及個人博客 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 接口就可以了。

這要求我們要用變化的角度去分析需求,看看哪些是變化更大的,將變化的東西使用類結構來承載。例如在生產薯條的例子中,生產新薯條的場景相對較少、新品牌可能變動,那么我們就應該將薯條類型作為產品族,這樣變化就不大。

總的來說,抽象工廠一般用在多個維度,即有產品族的情況下。產品族作為用第一層的抽象類來承載,但如果產品族變化很大則不適合使用抽象工廠。

總結

想到這里,小黑感覺知識間好像都關聯起來了。

  • 工廠方法是用來分類使用與創建的,創建對象使用工廠方法實現,創建的過程封裝在工廠類的方法中,我們不需要關心對象是怎么生產的。
  • 如果工廠方法只創建一種類型的對象,那么可以將工廠類簡化成帶靜態方法的工廠類,去掉工廠抽象類,減少類結構冗余。
  • 如果工廠方法要創建很多種類型的對象,而每種對象的創建過程都很復雜,那么就用多工種模式,即每種產品都對應一個工廠類,這就形成了多工廠模式。
  • 如果產品有多個產品族(兩個維度的變量),那么可以進一步抽象成抽象工廠模式。

總的來說,就是以工廠方法為基點,往前縮變成了簡單工廠,往后擴展變成了多工廠,往上一層就變成了抽象工廠。

注:在設計模式中,其實只有工廠方法模式和抽象工廠模式兩種。簡單工廠模式、多工廠模式、普通工廠方法,都屬於工廠方法。

文章首發於「陳樹義」公眾號及個人博客 shuyi.tech,歡迎訪問更多有趣有價值的文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM