使用工厂方法模式有哪些常见误区?
简化版
常见误区包括把简单对象也强行套工厂、工厂接口泄漏具体产品细节、用巨大条件分支冒充工厂方法,以及忽略对象生命周期和线程安全。工厂方法的目标是隔离变化的创建逻辑,不是给所有 new 都加一层包装。
详细版
高频误区可以按五类回答:
- 过度设计:产品很稳定,却为每个类都创建工厂;
- 抽象错误:产品接口包含具体实现才需要的方法;
- 分支膨胀:工厂方法内部仍然是一大段
if-else; - 生命周期混乱:该复用的对象每次创建,该隔离的对象做成单例;
- 依赖隐藏:业务类通过全局工厂偷偷获取依赖,测试困难。
正确使用时,先判断创建逻辑是否真的变化,再决定是否抽象工厂接口。如果只有一个简单构造器,直接 new 或依赖注入通常更清楚。
工厂方法也不自动解决线程安全。它只负责创建对象,创建出来的产品是否线程安全,还取决于产品内部状态和使用方式。
完整版教学
一、误区一:把模式当成目标
设计模式是解决变化和复杂度的工具,不是代码高级感的装饰。一个只有两个字段的值对象,直接构造往往最好。
如果引入工厂后,调用链更长、类型更多,但变化点没有被隔离,设计就没有带来收益。
二、误区二:工厂接口返回具体类
如果工厂方法返回具体类,调用方仍然依赖实现:
JsonParser createParser();
这样后续替换实现时,调用方可能也要改。更合理的是返回 Parser 接口,让具体类留在工厂内部。
三、误区三:中心分支继续膨胀
有些代码名叫工厂方法,实际仍然把所有产品写进一个 switch。这更接近简单工厂或注册表工厂,不是典型工厂方法。
如果产品持续增加,可以把创建逻辑拆到具体工厂,或者用注册表把 key 和工厂实现关联起来。
四、误区四:忽略生命周期
有些产品创建成本高、线程安全、可以复用;有些产品带请求状态,必须每次新建。工厂方法要考虑生命周期,而不是机械地 return new Xxx()。
在 Spring 中还要注意 Bean 作用域。把有状态产品做成单例,可能引发并发污染;把昂贵客户端每次创建,又可能造成性能问题。
五、用一次“新增产品”评审工厂是否真的可扩展
假设系统已有 JSON、XML 解析器,现在要新增 YAML。若修改点仍集中在一个不断增长的条件分支,或者业务代码必须强转成 YamlParser 才能使用,说明抽象边界并没有稳定下来。
旧调用方 -> ParserFactory 抽象
JsonParserFactory -> JsonParser
XmlParserFactory -> XmlParser
新增 YamlParserFactory -> YamlParser
旧产品契约测试保持不变
组合根新增注册或配置一处
业务解析流程无需出现 YamlParser 具体类
这段协作图要回答两个不同问题:产品由谁构造,具体工厂又由谁选择。工厂方法可以把产品构造从业务流程中移走,但应用入口、配置层或容器仍然必须决定使用哪个工厂;声称模式“消灭了所有分支”并不准确。
六、模式边界与扩展成本
| 维度 | 本题结论 |
|---|---|
| 稳定抽象 | 调用方真正需要的 Parser 行为和一个语义清楚的创建入口。 |
| 主要变化点 | 不同解析器的构造依赖、初始化步骤和生命周期。 |
| 新增一种产品的动作 | 新增具体产品与具体工厂,并在组合根/注册表接入。 |
| 不应放进工厂的职责 | 解析业务流程、全局 Service Locator、产品运行期算法编排。 |
| 更轻或更合适的方案 | 产品稳定且构造简单时直接 new 或交给依赖注入容器。 |
是否符合开闭原则不能只数新增了多少类,而要看高风险旧代码是否仍被反复修改。若每次扩展仍要修改一个中心 switch、产品接口又泄漏具体类型,即使类名叫 Factory,也没有获得工厂方法的主要收益。
七、用三个测试验证设计没有走样
- 契约测试:对每个具体工厂执行同一组产品接口测试,确认返回值满足抽象契约。
- 扩展测试:加入一个假产品和假工厂,记录为了接入它修改了哪些旧文件。
- 未知类型测试:若存在注册表或参数路由,明确缺失 key 的异常类型和降级策略。
- 生命周期测试:连续创建两次,确认产品应当复用还是隔离,避免无意变成单例或重复昂贵连接。
- 依赖测试:具体构造参数应由工厂显式获得,业务类不应通过全局容器偷偷查找。
- 并发测试:注册表若支持运行期写入,必须定义发布、覆盖和读取的一致性;只读注册表则尽量启动期冻结。
- 可观测性测试:记录命中的工厂 key 与具体实现,排查配置选择错误时无需猜测。
记忆钩子:工厂是否合格,看新增 YAML 时旧业务代码要不要认识 YamlParser。
八、常见误区与追问
- 误区:所有 new 都应该藏进工厂。 简单稳定对象直接构造通常更清楚。
- 误区:工厂接口返回具体类也能完全解耦。 调用方仍被具体类型绑定,替换实现会扩散修改。
- 误区:产品线程安全由工厂方法自动保证。 工厂只创建对象,产品内部并发语义要独立设计。
- 追问:中心 switch 一定是错误的吗? 产品少且稳定时简单工厂合理;变化频繁才值得拆分。
- 追问:为什么工厂不应承担业务流程? 创建与使用变化原因不同,混合后职责和测试范围都会膨胀。
- 追问:如何识别过度设计? 新增抽象没有隔离真实变化,只增加跳转层和样板类。
九、加强记忆
工厂方法最怕“为了模式而模式”。判断它是否用对了,就看三个问题:有没有真实创建变化点,调用方是否依赖抽象,新增产品是否降低了对旧代码的修改。