02 | 为什么加个新功能,要把旧代码改个底朝天?——工厂方法模式

0 阅读4分钟

记得刚入行那会儿,我特别怕碰到一种需求: “小王啊,咱们的消息推送,除了发短信,能不能再加个邮件推送?过两天可能还得加个微信通知。”

我当时的做法特别直接,在业务代码里写了一堆 if-else。 只要类型是 SMS,我就 new SMSProvider();是 Email,我就 new EmailProvider()

结果后来渠道越来越多,那个主函数的代码长得像老太婆的裹脚布。 更要命的是,每次加新渠道,我都得颤颤巍巍地去改那段核心逻辑,生怕手一抖,把原来的短信功能给搞挂了。

这其实是我们很多人的通病:把“造东西”和“用东西”这两件事,混在一起做了。

真正的高手,懂得把“麻烦”外包出去

工厂方法模式(Factory Method)的核心逻辑,其实就一句话: 把对象的创建过程剥离出来,交给一个专门的“工厂”去处理,业务逻辑只负责“提要求”。

这就好比你去咖啡店点单。 你只需要跟服务员说“我要一杯拿铁”,你不需要知道咖啡豆磨了多少秒,牛奶加热到了多少度,也不需要知道是哪位咖啡师做的。

如果哪天店里推出了新品“燕麦拿铁”,老板只需要培训咖啡师(工厂)就行了,你作为顾客(业务方),点单的方式完全不用变。

在代码里,这意味着我们要把那个到处 new 的动作,收拢到一个地方。

看看代码是怎么“减肥”的

咱们还是拿那个“消息推送”的例子来拆解。

如果不使用模式,我们的代码通常是这样的:

class MessageService {
  send(type, content) {
    // 这种写法在初期很常见,但隐患很大
    if (type === 'sms') {
      const service = new SMSService();
      service.send(content);
    } else if (type === 'email') {
      const service = new EmailService();
      service.send(content);
    }
    // 如果明天要加钉钉通知,你还得回来改这里
  }
}

这种写法的痛点在于:你的 MessageService 既要负责发消息的逻辑,又要负责创建服务的逻辑。违背了“单一职责原则”。

现在,我们试着用工厂模式重构一下:

// 1. 先定义好各种具体的服务
class SMSService { send(c) { console.log('短信:', c) } }
class EmailService { send(c) { console.log('邮件:', c) } }

// 2. 造一个工厂,专门负责“生产”这些服务
class NotificationFactory {
  static create(type) {
    const services = {
      'sms': SMSService,
      'email': EmailService
    };
    
    const ServiceClass = services[type];
    if (!ServiceClass) throw new Error('不支持的类型');
    
    return new ServiceClass();
  }
}

// 3. 业务逻辑变得极其清爽
class MessageService {
  send(type, content) {
    // 我不管你是怎么创建的,我只管找工厂要
    const service = NotificationFactory.create(type);
    service.send(content);
  }
}

两种写法的直观对比

容易出问题的写法: 核心业务逻辑里塞满了 if-elsenew后果:每次新增功能都要修改核心代码,测试范围无限扩大,很容易引入新 Bug。

更稳健的工厂写法: 核心业务只依赖一个 create 接口。 后果:如果要加一个“钉钉通知”,你只需要在工厂里注册一下,原来的 MessageService 代码连一个标点符号都不用动。这就是传说中的“对扩展开放,对修改关闭”。

给你的 3 条行动建议

  1. 识别“变化点”:检查一下你的代码,哪里有大量的 if-else 是为了判断“创建哪个对象”的?那里就是植入工厂模式的最佳地点。

  2. 别搞过度设计:如果是简单的两个类切换,用个简单的函数封装(简单工厂)就够了。别一上来就搞抽象类、接口继承那一套复杂的标准工厂模式,JS 没那么教条。

  3. 配置化思维:配合配置文件使用工厂模式简直是神器。比如把 type 写在 JSON 配置里,这样连代码都不用重新编译,改个配置就能切换系统的行为。

我以前总觉得把 new 拿出来单独写个类是多此一举。 后来项目维护了两年才发现,这种“多此一举”让我在面对老板突如其来的需求变更时,能从容地喝完手里的咖啡,而不是通宵改 Bug。