硬核!史上最全的工厂模式文章,从零到一全面讲解!

231 阅读10分钟

硬核!史上最全的工厂模式文章,从零到一全面讲解!

夏日沙滩

文章首发于「陈树义」公众号及个人博客 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();
    }
}

如无意外,结果应该是:

土豆去皮
土豆切成波纹
炸薯条

看起来这样的操作完全没问题,问题可以完美解决。不过小黑总觉得哪里不对劲,但又一时没想到原因。直到他听到店员吐苦水说:我就卖薯条的,你还要让我学怎么做薯条,多麻烦啊。还不如直接把薯条做好,我直接炸薯条就行。这样我就不用关心薯条怎么做的了。 下次做旋风薯条、麻辣薯条等等的时候,我也不用关心薯条怎么做,直接炸薯条就可以了。

工厂方法模式

听到这里小黑焕然大悟!作为售卖的店员来说,他不需要关注原材料怎么生产的,只需要知道怎么做好卖给顾客就可以了。这不就和我们编程中的工厂方法模式类似么?

工厂方法模式指的是使用者与创建者分离,使用者不需要知道对象是怎么创建出来的,而创建的过程封装在工厂里。 这就像这家炸鸡店一样,店员(使用者)不需要关心薯条怎么做出来的,薯条怎么做出来交给中央厨房(工厂)去做就可以了。

于是小黑调整了一下炸薯条的实现,使用工厂方法来实现。具体实现上,增加了一个工厂类来制作薯条。

// 工厂抽象类
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();
}

那么肯德基肯定有其对应的薯条工厂,麦当劳也有其薯条工厂。

// 肯德基薯条工厂
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 接口就可以了。

这要求我们要用变化的角度去分析需求,看看哪些是变化更大的,将变化的东西使用类结构来承载。例如在生产薯条的例子中,生产新薯条的场景相对较少、新品牌可能变动,那么我们就应该将薯条类型作为产品族,这样变化就不大。

总的来说,抽象工厂一般用在多个维度,即有产品族的情况下。产品族作为用第一层的抽象类来承载,但如果产品族变化很大则不适合使用抽象工厂。

总结

想到这里,小黑感觉知识间好像都关联起来了。

  • 工厂方法是用来分类使用与创建的,创建对象使用工厂方法实现,创建的过程封装在工厂类的方法中,我们不需要关心对象是怎么生产的。
  • 如果工厂方法只创建一种类型的对象,那么可以将工厂类简化成带静态方法的工厂类,去掉工厂抽象类,减少类结构冗余。
  • 如果工厂方法要创建很多种类型的对象,而每种对象的创建过程都很复杂,那么就用多工种模式,即每种产品都对应一个工厂类,这就形成了多工厂模式。
  • 如果产品有多个产品族(两个维度的变量),那么可以进一步抽象成抽象工厂模式。

总的来说,就是以工厂方法为基点,往前缩变成了简单工厂,往后扩展变成了多工厂,往上一层就变成了抽象工厂。

注:在设计模式中,其实只有工厂方法模式和抽象工厂模式两种。简单工厂模式、多工厂模式、普通工厂方法,都属于工厂方法。