← 返回题目列表

如何用工厂方法模式重构大量 if-else 创建逻辑?

高频 困难 第 15 / 25 题 更新于 2026/07/28
工厂方法模式if-else重构注册表代码重构

简化版

重构大量 if-else 时,先抽象产品接口,再把每个分支的创建逻辑拆成具体工厂,最后用配置、注册表或依赖注入完成工厂选择。重点是逐步迁移并补测试,避免一次性改动把创建逻辑和业务行为都搅在一起。

详细版

可以按五步做:

  1. 找出所有分支返回对象的共同能力,抽象产品接口;
  2. 把每个 if 分支中的构造细节移到具体工厂;
  3. 定义统一工厂接口,例如 HandlerFactory.create()
  4. Map<String, HandlerFactory> 替换中心分支;
  5. 为旧类型补回归测试,再逐个迁移新类型。

重构前:

Handler create(String type) {
    if ("email".equals(type)) return new EmailHandler(config);
    if ("sms".equals(type)) return new SmsHandler(client);
    if ("push".equals(type)) return new PushHandler(token);
    throw new IllegalArgumentException(type);
}

重构后:

HandlerFactory factory = factories.get(type);
if (factory == null) throw new IllegalArgumentException(type);
return factory.create();

这样中心代码只负责查找工厂,具体构造由各自工厂维护。新增产品时新增工厂并注册,旧分支不必继续膨胀。

完整版教学

一、先确认分支是不是同一种变化

不是所有 if-else 都适合用工厂方法。有些分支只是简单条件判断,没有对象创建变化;有些分支返回的对象能力完全不同,强行抽象会制造虚假的公共接口。

适合重构的信号是:多个分支都在创建同一抽象下的不同实现,而且新增类型越来越频繁。

二、抽象产品接口

先从调用方使用对象的方式提炼接口:

interface Handler {
    void handle(Request request);
}

接口只保留稳定行为,不把某个具体分支的配置细节塞进来。

三、拆分具体工厂

每个复杂分支可以拆成一个工厂:

class EmailHandlerFactory implements HandlerFactory {
    public Handler create() {
        return new EmailHandler(/* dependencies */);
    }
}

如果构造依赖很多,工厂本身可以通过构造器注入依赖,避免产品创建代码散落在业务流程里。

四、用注册表替换中心分支

中心创建器保留统一入口,但不再知道每个产品怎么创建:

class HandlerFactoryRegistry {
    private final Map<String, HandlerFactory> factories;

    Handler create(String type) {
        HandlerFactory factory = factories.get(type);
        if (factory == null) throw new IllegalArgumentException(type);
        return factory.create();
    }
}

这一步让新增产品变成“新增工厂 + 注册映射”,而不是改一段高风险分支。

五、迁移时要保护行为

重构创建逻辑很容易改变异常类型、默认值、依赖初始化顺序和对象生命周期。迁移前最好补齐旧分支测试,至少覆盖已支持的类型、未知类型、关键配置和并发使用场景。

例如旧代码遇到未知 type 时抛 UnsupportedChannelException,重构后就不应意外变成 NullPointerException;旧产品每请求新建,迁移后也不能无意注册成共享单例。把这些可观察行为写进测试,才能证明只是移动创建职责,而没有悄悄改变业务。

六、用第四种渠道验证重构后的修改面

已有 email、sms、push 三种 Handler 时,新增 webhook 是最直接的设计压力测试。理想结果是新建 WebhookHandler 和对应工厂,注册一次即可;旧三种 Handler 的构造路径与业务分发代码都不修改。

启动期: 注册 email -> EmailHandlerFactory
启动期: 注册 sms -> SmsHandlerFactory
启动期: 注册 push -> PushHandlerFactory
新增: 注册 webhook -> WebhookHandlerFactory
请求 type=webhook -> registry.get
factory.create(context) -> WebhookHandler
业务层只调用 Handler.handle(request)

这段协作图要回答两个不同问题:产品由谁构造,具体工厂又由谁选择。工厂方法可以把产品构造从业务流程中移走,但应用入口、配置层或容器仍然必须决定使用哪个工厂;声称模式“消灭了所有分支”并不准确。

七、模式边界与扩展成本

维度本题结论
稳定抽象Handler 表达所有渠道稳定处理能力,HandlerFactory 表达创建所需上下文。
主要变化点渠道专属依赖、配置、构造校验和产品类型数量。
新增一种产品的动作新增 Handler、Factory 和注册项;旧处理流程保持封闭。
不应放进工厂的职责渠道执行流程、请求路由后的业务判断和跨渠道编排。
更轻或更合适的方案若 Handler 本身无状态且由 Spring 管理,可直接注入 Map<String, Handler>,不必每次创建。

是否符合开闭原则不能只数新增了多少类,而要看高风险旧代码是否仍被反复修改。若每次扩展仍要修改一个中心 switch、产品接口又泄漏具体类型,即使类名叫 Factory,也没有获得工厂方法的主要收益。

八、用三个测试验证设计没有走样

  1. 契约测试:对每个具体工厂执行同一组产品接口测试,确认返回值满足抽象契约。
  2. 扩展测试:加入一个假产品和假工厂,记录为了接入它修改了哪些旧文件。
  3. 未知类型测试:若存在注册表或参数路由,明确缺失 key 的异常类型和降级策略。
  4. 生命周期测试:连续创建两次,确认产品应当复用还是隔离,避免无意变成单例或重复昂贵连接。
  5. 依赖测试:具体构造参数应由工厂显式获得,业务类不应通过全局容器偷偷查找。
  6. 并发测试:注册表若支持运行期写入,必须定义发布、覆盖和读取的一致性;只读注册表则尽量启动期冻结。
  7. 可观测性测试:记录命中的工厂 key 与具体实现,排查配置选择错误时无需猜测。

记忆钩子:重构不是把 if 换成 Map,而是让每个分支的构造知识有独立归属。

九、常见误区与追问

  • 误区:把 if-else 改成反射类名就完成重构。 反射字符串削弱类型安全,也没有处理依赖和生命周期。
  • 误区:注册表中应存产品实例而不是工厂。 取决于产品是否可复用;有请求状态时必须存创建能力。
  • 误区:所有条件分支都适合工厂化。 只有同一抽象下的创建变化才适合。
  • 追问:注册表如何避免运行期并发修改? 优先启动期构造不可变 Map;动态插件需要并发结构和明确覆盖规则。
  • 追问:如何渐进迁移旧分支? 先补字符化测试,再让旧入口委托注册表,逐种渠道迁移。
  • 追问:未知 type 应怎样处理? 显式抛业务异常或使用明确默认工厂,不能让空指针偶然决定行为。

十、加强记忆

用工厂方法重构 if-else 的顺序是先抽象产品,再拆具体工厂,最后用注册表或容器选择工厂。不要一开始就删分支,先让新旧行为对齐,重构才稳。