← 返回题目列表

如何把混乱的多厂商创建逻辑重构为抽象工厂?

高频 困难 第 15 / 25 题 更新于 2026/07/28
抽象工厂模式代码重构多厂商适配架构设计

简化版

重构时先识别产品族和产品等级,再抽象产品接口和抽象工厂接口,最后把每个厂商的创建逻辑拆到具体工厂中。迁移要配合测试,重点保护产品兼容关系、默认配置和异常行为。

详细版

重构步骤可以按六步走:

  1. 找出混在一起的厂商分支,例如存储、队列、短信客户端;
  2. 判断哪些对象属于同一产品族,哪些是产品等级;
  3. 为每个产品等级抽象接口,例如 StorageClientQueueClient
  4. 定义抽象工厂,例如 CloudFactory
  5. 拆出 AliCloudFactoryAwsCloudFactory 等具体工厂;
  6. 用配置或依赖注入选择具体工厂。

重构前可能是这样:

if ("ali".equals(provider)) {
    storage = new AliStorageClient(config);
    queue = new AliQueueClient(config);
} else if ("aws".equals(provider)) {
    storage = new AwsStorageClient(config);
    queue = new AwsQueueClient(config);
}

重构后,业务代码只拿一套工厂:

CloudFactory factory = cloudFactoryRouter.get(provider);
StorageClient storage = factory.createStorage();
QueueClient queue = factory.createQueue();

这样新增厂商时主要新增具体产品和具体工厂,而不是继续扩展一大段中心分支。

完整版教学

一、先识别真实产品族

重构前不要急着写接口。先看这些对象是不是必须成套出现:阿里云存储是否应该搭配阿里云队列,AWS 存储是否应该搭配 AWS 队列。

如果答案是肯定的,它们就有产品族边界。

二、提炼稳定产品接口

产品接口要面向业务需要,而不是照搬厂商 SDK。比如业务只需要上传和下载,就不要把厂商 SDK 的所有高级能力都暴露出来。

interface StorageClient {
    void upload(String bucket, String key, byte[] data);
}

接口越贴近业务稳定能力,后续切换厂商越轻。

三、拆分具体工厂

每个具体工厂负责一个厂商的一组产品创建。厂商配置、凭证、region、连接池等细节可以封装在具体工厂里。

这能让业务代码从“理解所有厂商创建细节”变成“选择一个厂商工厂”。

四、设计工厂选择入口

工厂选择可以来自配置文件、Spring 条件装配、注册表或路由器。选择入口最好集中,避免业务代码到处写 if provider == ...

如果是多租户系统,路由器还要考虑租户配置缓存、凭证刷新和隔离问题。

五、补测试保护行为

重构创建逻辑容易改变默认超时、重试策略、异常类型和资源释放方式。要为每个厂商至少覆盖创建成功、配置缺失、未知厂商、关键调用路径。

这一步是工程落地里最容易被忽略的。

六、先用兼容矩阵固定现状,再移动创建代码

重构多厂商代码前先列出 provider×能力矩阵和实际配置差异,防止把历史上可混搭或缺失的能力错误包装成完整产品族。测试先锁定超时、异常和关闭行为,再逐厂商迁移。

          Storage  Queue  Sms
Ali       有       有     有
AWS       有       有     依业务适配
Legacy    有       无     无
先确认哪些行真能履行统一契约
提取稳定产品接口
每次迁移一个具体厂商工厂
最后把中心 if 收缩为工厂路由

矩阵的每一行是一族兼容产品,每一列是一个产品等级。客户端选择一行对应的具体工厂,再从该工厂取得各列产品;如果允许调用方绕过工厂分别选择每个具体类,产品族一致性的结构性保证就消失了。

七、二维扩展成本与模式边界

评审维度本题结论
产品族不变量同一 provider 工厂创建的产品使用一致配置,并保持重构前可观察行为。
新增一行(产品族)在契约测试保护下增加具体产品、工厂和路由注册。
新增一列(产品等级)先验证所有厂商是否支持;不稳定能力宜拆分而非强塞主接口。
不应由工厂承担改变超时/重试语义、吞异常或同时重写业务流程。
退回更轻方案的条件只有一个对象存在厂商差异时用工厂方法;能力差异过大时用独立适配器组合。

抽象工厂对所有变化并非都开放。它刻意固定列集合,以换取整行切换的一致性;如果系统最常发生的是新增列,工厂接口和所有行实现会频繁联动,此时应拆分工厂边界或选择组合方式。

八、落地验收清单

  1. 为每个具体工厂跑同一组契约测试,确认每个创建方法都返回正确产品等级。
  2. 给产品暴露 familyId() 仅用于测试,校验同一工厂创建的所有产品 familyId 一致。
  3. 加入一个假产品族,记录是否只新增一行实现和装配配置,而未修改旧工厂。
  4. 模拟新增一个产品等级,评估抽象接口和全部具体工厂的真实修改成本。
  5. 检查公共产品接口是否出现 AWS、MySQL、Windows 等具体实现词汇。
  6. 检查具体工厂是否只做组装与创建,没有吞入完整业务流程。
  7. 若产品持有连接或线程池,明确由工厂、容器还是调用方负责关闭。
  8. 若运行期按租户切换工厂,验证路由缓存、凭证刷新和租户隔离。

记忆钩子:先证明它们真是一族,再抽接口;重构模式不能顺便改业务语义。

九、常见误区与追问

  • 误区:先写 AbstractFactory 再分析厂商能力。 容易得到虚假公共接口和大量不支持实现。
  • 误区:重构后异常类型改变无所谓。 调用方可能依赖异常和重试语义,必须有测试保护。
  • 误区:所有 provider 分支都应一次删除。 渐进委托和逐族迁移更容易控制风险。
  • 追问:如何验证没有混搭? 让工厂产品带测试用 familyId,执行组合契约测试。
  • 追问:默认配置放哪里? 稳定业务默认可在配置层,厂商细节放具体工厂,避免散落调用方。
  • 追问:未知厂商如何处理? 路由器应给出明确配置错误,不能静默回退到随机默认族。

十、加强记忆

重构到抽象工厂的顺序是先找产品族,再抽象产品等级,最后拆具体工厂和选择入口。只要产品不能成套出现,抽象工厂就没有抓到真正问题。