设计模式与生活②:你只管点外卖,别管堡怎么做

设计模式与生活②:你只管点外卖,别管堡怎么做

Scroll Down

设计模式与生活②:你只管点外卖,别管堡怎么做

周日上午,小王大扫除。扫地拖地、收拾屋子,一通忙活下来,整个人瘫在沙发上。

然后饿了。

两个选择:自己做饭——买菜、洗菜、切配、掌勺、洗碗,一套下来,一两个小时就没了;或者打开外卖 App,点一份"麦辣鸡腿堡"。

他选了后者。三十分钟后,饭到了。

scene-clean

大扫除后的哲学家:能不下厨,就不下厨

这半小时里他没进厨房,不知道鸡腿肉是谁裹粉炸的、油温烧到多少度、酱料挤了几泵。他只管"要什么",不管"怎么造"。

今天聊的,就是这件事在代码里的样子:工厂模式(Factory Pattern)——把"造对象"这件事,从"用对象"的人手里拿走。系列第二篇,从小王这顿外卖开始。

01 · 自己 new,就是自己下厨

代码里的"自己下厨",长这样:

// 想吃麦辣鸡腿堡,自己 new:备料、腌制、油炸、组装全包
Meal meal = new McSpicyBurger("芝麻包", "炸鸡腿肉", "双倍辣酱");

// 构造器改一个参数,所有"自己下厨"的地方都得跟着改

自己 new:调用方和后厨死绑

想吃这个堡,你得先拿到配方:鸡腿肉腌多久、裹多厚的粉、油温烧到多少度——构造器的每个参数你都得懂。哪天 McSpicyBurger 改了构造签名,所有自己 new 过它的地方,全部跟着改。

一个类被 new 了一百次,就长出一百根绳子,把一百个调用方和它捆死在一起。

02 · 简单工厂:你报名字,后厨来做

麦当劳的做法,是把"怎么做"收进后厨。你只管报名字:

public class MealFactory {

    // 后厨:你报名字,它来做
    public static Meal create(String name) {
        switch (name) {
            case "麦辣鸡腿堡": return new McSpicyBurger();
            case "板烧鸡腿堡": return new GrilledChickenBurger();
            case "巨无霸": return new BigMac();
            default: throw new IllegalArgumentException("这款堡不会");
        }
    }
}

// 你只管点,后厨的事不操心
Meal meal = MealFactory.create("麦辣鸡腿堡");

简单工厂:名字进,对象出

调用方从此不认识 McSpicyBurger,只认识两样东西:菜单(Meal 接口)和这家店(MealFactory)。创建和使用,分家了。

factory-map-1792898756865

左边是你点外卖,右边是简单工厂

得挑明一个类比的失真点:点外卖会踩雷——图片上的堡又大又挺,到手就瘪了;代码里的工厂不会缩水,它返回的对象一定满足接口约定,说是什么就是什么。代码里的后厨,比外卖店守信用。

再问一步:菜单越写越长,会怎样?

03 · 工厂方法:一款汉堡,开一条产线

菜单从 3 款加到 30 款,后厨那个 switch 就写出 30 个分支。更要命的是:每上一款新品,都得改后厨——改错一行,全店停业。

这撞上了设计里的一条铁律:对扩展开放,对修改关闭。

解决办法:一款汉堡,开一条专属产线。 总部只定规矩——是条产线就得会出堡;具体做哪款,各产线自己说了算:

// 总部定的规矩:是条产线,就得会出堡
interface BurgerLine {
    Meal cook();
}

// 麦辣鸡腿堡产线
class McSpicyLine implements BurgerLine {
    public Meal cook() {
        return new McSpicyBurger();
    }
}

// 板烧鸡腿堡产线
class GrilledLine implements BurgerLine {
    public Meal cook() {
        return new GrilledChickenBurger();
    }
}

工厂方法:上新品 = 新拉产线,老线不动

上新品 = 新拉一条产线,老产线的按钮一个不动。这就是工厂方法(Factory Method):把"造哪个"的决定,推迟给子类。

再问一步:光一个堡,够吗?

04 · 抽象工厂:套餐,讲究的是一整套配套

小王后来不点单品了,改点套餐。套餐的讲究不在单个堡,在搭配:麦当劳套餐配可口可乐,肯德基套餐配百事可乐——两大阵营签的都是排他协议,混不了。

配套不能错——麦当劳的杯子里倒出百事可乐,店员自己先慌了。

**抽象工厂(Abstract Factory)**管的,就是这一整套配套:

// 套餐工厂:一整套搭配,保证不串味
interface ComboFactory {
    Burger burger();    // 堡
    Fries fries();      // 薯条
    Drink drink();      // 可乐
}

// 麦当劳套餐
class McDonaldsCombo implements ComboFactory {
    public Burger burger() { return new McSpicyBurger(); }
    public Fries fries()   { return new McFries(); }
    public Drink drink()   { return new Coke(); }
}

// 肯德基套餐
class KfcCombo implements ComboFactory {
    public Burger burger() { return new ZingerBurger(); }
    public Fries fries()   { return new KfcFries(); }
    public Drink drink()   { return new Pepsi(); }
}

抽象工厂:换套餐 = 换工厂,整套一次换

三种工厂讲完,一张图收个尾:

evolution

一家店 → 专门店一条街 → 套餐档口

05 · 这些"后厨",你天天在用

  • 📅 JDK 的 Calendar.getInstance():你不管它背后是公历还是佛历,拿来就用——简单工厂的思路;
  • 🔢 Integer.valueOf():《Effective Java》第 1 条就劝你:优先考虑静态工厂方法,别上来就 new;
  • 📋 SLF4J 的 LoggerFactory.getLogger():你只管要个 logger,背后是 Logback 还是 Log4j2,不用你操心;
  • 🌱 Spring 整个容器就是个超级大工厂:BeanFactory / ApplicationContext,你声明要什么 Bean,它负责造。

06 · 什么时候,别建工厂

说点边界。还是那个周日——小王擦桌子,拿块抹布蘸水就擦了。他不会为擦桌子先定义 CleaningTool 接口、写个 RagFactory、再 create 一块抹布出来。

自己就煎个蛋,犯不上先拉一条产线。

判断标准就一条:创建这件事,复杂吗?会变吗?

构造器一长串参数、创建要读配置、连资源、攒依赖——值得建工厂;

new ArrayList<>() 这种一行搞定、十年不变的——直接 new,别套。

工厂模式最常被骂的滥用,就是给每个类都配个工厂,类数量翻倍,读代码像剥洋葱。模式是工具,不是奖状——用了不涨工资。

最后跟系列第一篇呼应一下:工厂只管"怎么造",不管"造几个"。点两次外卖来两份,天经地义;想让全系统只有一份,那是单例的活,别混。

带走一句话:工厂模式 = 把"怎么做"关进后厨——你只管报名字(接口),做堡(new)的事,工厂包了。

好的代码和好的外卖一样:你享受结果,不必进厨房。

参考资料

  1. Erich Gamma 等,《设计模式:可复用面向对象软件的基础》(GoF 原著)——工厂方法、抽象工厂的原始出处
  2. 《Head First 设计模式》第 4 章——用披萨店讲工厂,本文"外卖店"类比的同乡
  3. Joshua Bloch,《Effective Java(第 3 版)》——第 1 条:考虑用静态工厂方法代替构造器(本文反例段的依据)
  4. Spring 官方文档 · IoC 容器