工厂方法和抽象工厂有什么区别?
简化版
工厂方法通常关注创建一种产品,由具体工厂决定返回哪个具体产品;抽象工厂关注创建一组相关产品,也就是产品族。工厂方法是一条产品线的扩展,抽象工厂是多条相关产品线的一致创建。
详细版
区别可以这样记:
| 维度 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 创建对象 | 一类产品 | 一组相关产品 |
| 工厂接口 | 通常一个创建方法 | 多个创建方法 |
| 变化重点 | 新增同一等级的产品 | 切换整套产品族 |
| 例子 | ParserFactory.createParser() | UIFactory.createButton() + createCheckbox() |
工厂方法例子:
interface ParserFactory {
Parser createParser();
}
抽象工厂例子:
interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
如果只需要创建 Parser 一种产品,工厂方法够用;如果要保证 Windows 按钮和 Windows 复选框成套出现,Mac 按钮和 Mac 复选框成套出现,就更适合抽象工厂。
完整版教学
一、先理解产品等级和产品族
产品等级指同一类产品的不同实现,例如 Button 有 WindowsButton、MacButton。产品族指属于同一风格或平台的一组产品,例如 Windows 风格下的 Button、Checkbox、Menu。
工厂方法主要处理产品等级扩展,抽象工厂主要处理产品族一致性。
二、工厂方法适合一类产品
当系统只关心创建某一类对象时,工厂方法就足够。比如不同格式的解析器都实现 Parser,新增 CSV 解析器时新增 CsvParserFactory。
这时引入抽象工厂会显得多余,因为没有一组相关产品需要同时创建。
三、抽象工厂适合成套产品
如果业务要求一组对象必须来自同一风格、协议或平台,抽象工厂更合适。比如 UI 组件、数据库方言对象、云厂商 SDK 适配对象。
interface CloudFactory {
StorageClient createStorage();
QueueClient createQueue();
}
阿里云工厂创建阿里云的一组客户端,AWS 工厂创建 AWS 的一组客户端,调用方不用担心混用。
四、两者可以组合使用
抽象工厂中的每个创建方法本身也可以看作工厂方法。也就是说,抽象工厂不是工厂方法的反面,而是在更高维度组织多个工厂方法。
面试里可以用这句话收束:工厂方法解决“一个产品怎么扩展”,抽象工厂解决“一组产品怎么保持一致”。
五、用二维产品矩阵决定需要一条创建线还是一整族
只有 JSON、XML、CSV 三种 Parser 时,产品只有一个等级,工厂方法足够;若每个平台都要成套创建 Parser、Validator、Serializer,且同平台对象不能混搭,才进入抽象工厂的产品族问题。
单等级: Parser <- JsonParser / XmlParser
工厂方法: JsonParserFactory.createParser()
多等级: Parser + Validator + Serializer
产品族 A: AParser + AValidator + ASerializer
产品族 B: BParser + BValidator + BSerializer
抽象工厂: createParser/createValidator/createSerializer
客户端选择一族工厂后成套创建
这段协作图要回答两个不同问题:产品由谁构造,具体工厂又由谁选择。工厂方法可以把产品构造从业务流程中移走,但应用入口、配置层或容器仍然必须决定使用哪个工厂;声称模式“消灭了所有分支”并不准确。
六、模式边界与扩展成本
| 维度 | 本题结论 |
|---|---|
| 稳定抽象 | 工厂方法稳定一个产品接口;抽象工厂稳定多个相关产品接口。 |
| 主要变化点 | 前者主要变化具体产品,后者主要变化整套产品族。 |
| 新增一种产品的动作 | 单产品新增具体工厂;产品族新增一个实现所有创建方法的具体抽象工厂。 |
| 不应放进工厂的职责 | 把没有兼容关系的产品硬塞成一族。 |
| 更轻或更合适的方案 | 只有一类产品用工厂方法;相关产品可独立选择时用多个独立工厂。 |
是否符合开闭原则不能只数新增了多少类,而要看高风险旧代码是否仍被反复修改。若每次扩展仍要修改一个中心 switch、产品接口又泄漏具体类型,即使类名叫 Factory,也没有获得工厂方法的主要收益。
七、用三个测试验证设计没有走样
- 契约测试:对每个具体工厂执行同一组产品接口测试,确认返回值满足抽象契约。
- 扩展测试:加入一个假产品和假工厂,记录为了接入它修改了哪些旧文件。
- 未知类型测试:若存在注册表或参数路由,明确缺失 key 的异常类型和降级策略。
- 生命周期测试:连续创建两次,确认产品应当复用还是隔离,避免无意变成单例或重复昂贵连接。
- 依赖测试:具体构造参数应由工厂显式获得,业务类不应通过全局容器偷偷查找。
- 并发测试:注册表若支持运行期写入,必须定义发布、覆盖和读取的一致性;只读注册表则尽量启动期冻结。
- 可观测性测试:记录命中的工厂 key 与具体实现,排查配置选择错误时无需猜测。
记忆钩子:一条产品等级线用工厂方法,必须成套切换的多条线用抽象工厂。
八、常见误区与追问
- 误区:抽象工厂只是返回类型写成接口的工厂方法。 它的辨识点是创建一组相关产品并维持产品族一致性。
- 误区:工厂方法只能创建一个对象实例。 它可被调用多次;“一种产品”指产品等级,不是数量。
- 误区:两个模式不能组合。 抽象工厂中的每个创建方法本身就具有工厂方法性质。
- 追问:新增产品等级谁更痛苦? 抽象工厂需修改接口和所有具体工厂,影响面更大。
- 追问:新增产品族谁更自然? 抽象工厂新增一个具体工厂及整套具体产品即可。
- 追问:如何避免抽象工厂接口膨胀? 按稳定产品族边界拆分,不相关或频繁变化的等级使用独立工厂。
九、加强记忆
看到“创建一种产品”,优先想到工厂方法;看到“一整套相关产品不能混搭”,优先想到抽象工厂。前者是一条线,后者是一整组线。