抽象工厂如何支持运行时切换产品族?
简化版
抽象工厂可以把产品族选择集中到配置、环境变量、租户信息或策略路由中,运行时根据条件选择具体工厂,再由该工厂创建整套产品。关键是切换边界要清楚:同一次请求或同一个租户内应使用同一个工厂,避免一半产品来自旧族、一半来自新族。
详细版
运行时切换常见于多云适配、多数据库方言、跨平台 UI、灰度环境和多租户系统。做法通常是维护一个 FactoryProvider 或注册表,根据上下文选择具体抽象工厂:
CloudFactory factory = factoryProvider.getFactory(tenantId);
StorageClient storage = factory.createStorageClient();
QueueClient queue = factory.createQueueClient();
切换时要注意:
- 工厂选择应在一个明确边界内完成,例如请求开始、任务开始或租户初始化;
- 同一业务流程内不要多次读取可变配置导致工厂不一致;
- 工厂本身若持有资源,要处理缓存、刷新和关闭;
- 灰度切换要有回滚和观测指标。
运行时切换不是把 if-else 到处散开,而是把选择点集中起来,让业务代码仍依赖抽象工厂和抽象产品。
完整版教学
一、运行时切换解决什么问题
抽象工厂的基本版本常在启动时确定一个产品族,比如当前平台是 Windows,就使用 WindowsUiFactory。但真实项目里,产品族可能需要按租户、区域、灰度策略或请求参数动态选择。
例如同一个 SaaS 系统里:
租户 A -> AWS 产品族
租户 B -> 阿里云产品族
租户 C -> 本地私有云产品族
如果选择逻辑散落在每个业务类中,就会出现存储走 AWS、队列走阿里云的混搭风险。运行时抽象工厂的目标是集中选择,并让后续产品都来自同一个具体工厂。
二、FactoryProvider 是常见落地方式
可以增加一个工厂提供者,用它根据上下文返回具体工厂。业务流程只在入口处选择一次。
public class CloudFactoryProvider {
private final Map<String, CloudFactory> factories;
public CloudFactory getFactory(String vendor) {
CloudFactory factory = factories.get(vendor);
if (factory == null) {
throw new IllegalArgumentException("unknown vendor: " + vendor);
}
return factory;
}
}
请求处理时:
CloudFactory factory = provider.getFactory(request.vendor());
StorageClient storage = factory.createStorageClient();
QueueClient queue = factory.createQueueClient();
MonitorClient monitor = factory.createMonitorClient();
这个写法把运行时条件限制在工厂选择处,而不是让三个产品各自读取配置。
三、同一次流程内为什么要固定工厂
如果业务流程中每一步都重新读取配置,切换窗口就可能产生不一致。比如 T1 时读到 aws 创建了 storage,T2 时配置变成 aliyun 创建了 queue,最后同一订单同时使用两个云厂商。
T=0 请求开始,配置 vendor=aws
T=1 创建 AwsStorageClient
T=2 配置灰度切到 aliyun
T=3 创建 AliyunQueueClient
T=4 同一流程产品族混搭
更稳的做法是在请求开始时解析出 CloudFactory,并把它作为上下文传递下去。同一流程内不再重新选择工厂,避免半路切换造成组合不一致。
记忆钩子:运行时可以切族,但一次业务流程里不要边跑边换族。
四、多租户场景要注意缓存 key
多租户切换常需要缓存具体工厂或产品对象。缓存 key 必须包含影响创建结果的维度,例如租户、厂商、region、凭证版本。key 少一维就可能串租户。
record FactoryKey(String tenantId, String vendor, String region, String credentialVersion) {}
假设有 100 个租户,每个租户 2 个 region,如果只按 vendor 缓存,就会把不同租户的凭证混用。正确缓存规模可能是 100 * 2 = 200 个工厂或配置对象,而不是 2 个厂商对象。
当然,缓存规模也要控制。工厂如果持有连接池和线程池,200 个工厂可能带来明显资源压力,需要惰性创建、淘汰、共享底层连接或交给容器管理。
五、灰度切换要考虑回滚和观测
运行时切换常用于灰度发布,比如 5% 租户从旧云厂商切到新云厂商。抽象工厂可以把灰度选择集中在 provider 中,但还需要指标验证。
建议观测:
| 指标 | 作用 |
|---|---|
| 每个产品族请求量 | 确认流量比例正确 |
| 创建失败率 | 发现新工厂初始化问题 |
| 产品调用错误码 | 比较新旧产品族质量 |
| 回滚耗时 | 验证配置切回是否生效 |
如果灰度期间发现新产品族失败率从 0.1% 升到 3%,provider 应能快速切回旧工厂。抽象工厂解决结构切换,灰度系统解决风险控制。
六、和策略模式有什么区别
运行时选择具体工厂,看起来像策略模式。区别在于:策略模式通常选择一个算法行为,抽象工厂选择一组相关产品。一个租户选择 AwsFactory 后,会得到 AWS 的 storage、queue、monitor 一整套产品。
| 维度 | 策略模式 | 运行时抽象工厂 |
|---|---|---|
| 选择对象 | 一个算法/行为 | 一组产品创建者 |
| 一致性重点 | 行为可替换 | 产品族配套 |
| 典型例子 | 折扣算法 | 多云客户端族 |
| 切换边界 | 单次算法调用 | 请求、租户、环境 |
如果只切换一个算法,用策略就够;如果切换后要创建整套兼容对象,抽象工厂更合适。
七、常见误区与追问
- 误区:运行时切换就是到处写 if-else。 正确做法是集中选择具体工厂,业务代码仍依赖抽象。
- 误区:每次创建产品都重新读配置更灵活。 同一流程内多次读取可变配置会造成产品族混搭。
- 误区:工厂缓存只按 vendor 就够。 多租户、多 region、多凭证版本都可能影响创建结果。
- 误区:抽象工厂能自动完成灰度安全。 它只提供结构,灰度比例、指标和回滚仍要单独设计。
- 追问:工厂选择应该放在哪里? 放在请求入口、任务入口、租户上下文初始化等明确边界。
- 追问:如何避免切换期间旧资源泄漏? 对被替换工厂做引用计数、延迟关闭或容器生命周期管理。
- 追问:运行时切换和策略模式怎么区分? 策略切一个行为,抽象工厂切一整套产品族。
八、加强记忆
运行时抽象工厂的核心是“集中选族,流程内固定”。按配置、租户或灰度选择具体工厂后,一整套产品都从这个工厂创建;同时要处理缓存 key、资源释放、观测回滚和与策略模式的边界。