上一篇讲了工厂方法模式。
它解决的问题是:
一个对象的创建逻辑,不应该到处散落在业务代码里。
比如诊断服务需要创建不同通信对象:
- CAN 通信对象
- DoIP 通信对象
- 串口通信对象
- 仿真通信对象
这时可以用工厂方法,把"创建哪个通信对象"这件事从业务流程里拆出去。
但真实工程里,经常不只是创建一个对象。
很多时候,我们要创建的是一整套相关对象。
比如同一套车载软件,要适配不同车型平台:
A 平台可能需要:
ABrakeControllerASteeringControllerABatteryManagerACanTransport
B 平台可能需要:
BBrakeControllerBSteeringControllerBBatteryManagerBDoIPTransport
这时问题就变了。
我们不只是关心:
某一个对象怎么创建?
而是关心:
一整套彼此匹配的对象,怎么统一创建?
这就是抽象工厂模式要解决的问题。
一、先从一个车企场景说起
假设我们现在要做一个整车控制系统。
这个系统要支持两个平台:
- A 平台
- B 平台
两个平台都有制动控制、转向控制、电池管理这些模块。
但它们的底层实现不一样。
A 平台使用的是 A 供应商的控制接口。
B 平台使用的是 B 供应商的控制接口。
如果业务代码直接写成这样:
cpp
class VehicleSystem {
public:
void Init(int platformType) {
if (platformType == 1) {
m_brake = new ABrakeController();
m_steering = new ASteeringController();
m_battery = new ABatteryManager();
} else if (platformType == 2) {
m_brake = new BBrakeController();
m_steering = new BSteeringController();
m_battery = new BBatteryManager();
}
}
private:
IBrakeController* m_brake;
ISteeringController* m_steering;
IBatteryManager* m_battery;
};
这段代码能跑。
但它的问题很明显。
第一,VehicleSystem 不只负责整车系统初始化,还负责判断平台、创建具体对象。
第二,平台相关的创建逻辑全部塞在主流程里。
第三,如果新增 C 平台,就要修改 VehicleSystem。
第四,如果某个地方不小心混用了 A 平台制动控制器和 B 平台转向控制器,系统可能会出现很隐蔽的问题。
比如:
text
ABrakeController
BSteeringController
ABatteryManager
这几个对象各自都能创建,但它们不一定属于同一套平台。
也就是说,问题不只是"怎么创建对象"。
更关键的是:
如何保证创建出来的一组对象,是同一个产品族里的对象。
抽象工厂模式,就是为了解决这个问题。
二、为什么工厂方法还不够?
工厂方法适合解决"一个产品"的创建问题。
比如:
text
创建一个通信对象
创建一个传感器对象
创建一个控制器对象
但当系统里出现"一组相关对象"时,工厂方法就会有点不够用了。
比如整车平台里,不同模块之间往往不是孤立的。
制动控制器、转向控制器、电池管理器、通信对象,可能都和平台有关。
它们之间要匹配:
text
A 平台制动控制器
A 平台转向控制器
A 平台电池管理器
A 平台通信对象
或者:
text
B 平台制动控制器
B 平台转向控制器
B 平台电池管理器
B 平台通信对象
如果每个对象都各自用一个工厂方法来创建,就可能出现一个问题:
单个对象能创建,但对象之间的组合关系没有被约束住。
比如:
cpp
auto brake = brakeFactory.CreateBrakeController();
auto steering = steeringFactory.CreateSteeringController();
auto battery = batteryFactory.CreateBatteryManager();
如果这些工厂来自不同平台,就容易创建出"不匹配的一组对象"。
所以,抽象工厂要解决的是更高一层的问题:
不只是创建对象,而是创建一整套相互匹配的对象族。
三、抽象工厂模式到底是什么?
抽象工厂模式可以这样理解:
提供一个创建一组相关或相互依赖对象的接口,让调用方不需要指定它们的具体类。
再说得直白一点:
调用方只选择"哪一套工厂",然后通过这套工厂创建一整组对象。
例如:
选择 A 平台工厂,就创建 A 平台的一整套对象。
选择 B 平台工厂,就创建 B 平台的一整套对象。
业务代码不需要知道:
- A 平台制动控制器具体类叫什么
- B 平台转向控制器怎么构造
- 电池管理器需要哪些供应商参数
- 通信对象到底用 CAN 还是 DoIP
这些创建细节都由具体工厂负责。
四、抽象工厂模式的核心角色
抽象工厂模式里通常有四类角色。
1. 抽象产品
定义一类产品的统一接口。
比如:
text
IBrakeController
ISteeringController
IBatteryManager
它们分别代表制动控制、转向控制、电池管理的抽象能力。
2. 具体产品
具体平台下的产品实现。
比如:
text
ABrakeController
ASteeringController
ABatteryManager
BBrakeController
BSteeringController
BBatteryManager
它们属于不同产品族。
3. 抽象工厂
定义一组创建方法。
比如:
text
CreateBrakeController()
CreateSteeringController()
CreateBatteryManager()
它不关心具体创建 A 平台还是 B 平台。
4. 具体工厂
负责创建某一个产品族。
比如:
text
APlatformFactory
BPlatformFactory
APlatformFactory 创建 A 平台一整套对象。
BPlatformFactory 创建 B 平台一整套对象。
五、抽象工厂模式的结构
可以用一个简化结构理解:
text
业务系统
↓ 使用
抽象工厂 IVehicleFactory
↓ 创建
抽象产品 IBrakeController / ISteeringController / IBatteryManager
APlatformFactory
→ ABrakeController
→ ASteeringController
→ ABatteryManager
BPlatformFactory
→ BBrakeController
→ BSteeringController
→ BBatteryManager
这张结构里,最重要的不是类多,而是三件事:
第一,业务系统只依赖抽象工厂和抽象产品。
第二,具体工厂负责创建同一产品族里的具体对象。
第三,新增平台时,优先新增一套具体工厂和具体产品,而不是修改业务主流程。
抽象工厂隔离的变化点是:
一整套产品族的创建变化。
六、一个 C++ 示例:不同平台的一套控制对象
先定义抽象产品。
制动控制接口
cpp
class IBrakeController {
public:
virtual ~IBrakeController() = default;
virtual void ApplyBrake() = 0;
};
转向控制接口
cpp
class ISteeringController {
public:
virtual ~ISteeringController() = default;
virtual void TurnLeft() = 0;
};
电池管理接口
cpp
class IBatteryManager {
public:
virtual ~IBatteryManager() = default;
virtual int GetSoc() = 0;
};
然后定义 A 平台产品。
cpp
class ABrakeController : public IBrakeController {
public:
void ApplyBrake() override {
// A platform brake control
}
};
class ASteeringController : public ISteeringController {
public:
void TurnLeft() override {
// A platform steering control
}
};
class ABatteryManager : public IBatteryManager {
public:
int GetSoc() override {
// A platform battery soc
return 80;
}
};
再定义 B 平台产品。
cpp
class BBrakeController : public IBrakeController {
public:
void ApplyBrake() override {
// B platform brake control
}
};
class BSteeringController : public ISteeringController {
public:
void TurnLeft() override {
// B platform steering control
}
};
class BBatteryManager : public IBatteryManager {
public:
int GetSoc() override {
// B platform battery soc
return 75;
}
};
接着定义抽象工厂。
cpp
class IVehicleFactory {
public:
virtual ~IVehicleFactory() = default;
virtual std::unique_ptr<IBrakeController> CreateBrakeController() = 0;
virtual std::unique_ptr<ISteeringController> CreateSteeringController() = 0;
virtual std::unique_ptr<IBatteryManager> CreateBatteryManager() = 0;
};
再定义具体工厂。
cpp
class APlatformFactory : public IVehicleFactory {
public:
std::unique_ptr<IBrakeController> CreateBrakeController() override {
return std::make_unique<ABrakeController>();
}
std::unique_ptr<ISteeringController> CreateSteeringController() override {
return std::make_unique<ASteeringController>();
}
std::unique_ptr<IBatteryManager> CreateBatteryManager() override {
return std::make_unique<ABatteryManager>();
}
};
class BPlatformFactory : public IVehicleFactory {
public:
std::unique_ptr<IBrakeController> CreateBrakeController() override {
return std::make_unique<BBrakeController>();
}
std::unique_ptr<ISteeringController> CreateSteeringController() override {
return std::make_unique<BSteeringController>();
}
std::unique_ptr<IBatteryManager> CreateBatteryManager() override {
return std::make_unique<BBatteryManager>();
}
};
业务系统就可以这样写:
cpp
class VehicleSystem {
public:
explicit VehicleSystem(IVehicleFactory& factory) {
m_brake = factory.CreateBrakeController();
m_steering = factory.CreateSteeringController();
m_battery = factory.CreateBatteryManager();
}
void Run() {
m_brake->ApplyBrake();
m_steering->TurnLeft();
int soc = m_battery->GetSoc();
}
private:
std::unique_ptr<IBrakeController> m_brake;
std::unique_ptr<ISteeringController> m_steering;
std::unique_ptr<IBatteryManager> m_battery;
};
使用 A 平台:
cpp
APlatformFactory factory;
VehicleSystem system(factory);
system.Run();
使用 B 平台:
cpp
BPlatformFactory factory;
VehicleSystem system(factory);
system.Run();
你会发现,VehicleSystem 不需要知道自己使用的是 A 平台还是 B 平台。
它只知道:
我需要一套制动、转向、电池管理对象。
具体是哪一套,由工厂决定。
这就是抽象工厂模式的核心价值。
七、这个例子到底好在哪里?
1. 平台相关创建逻辑被集中管理
原来平台判断可能散落在业务代码里:
text
if A 平台,创建 ABrakeController
if B 平台,创建 BBrakeController
if A 平台,创建 ASteeringController
if B 平台,创建 BSteeringController
if A 平台,创建 ABatteryManager
if B 平台,创建 BBatteryManager
现在这些逻辑都集中到了具体工厂里。
A 平台对象由 APlatformFactory 统一创建。
B 平台对象由 BPlatformFactory 统一创建。
这让平台差异更清楚。
2. 保证对象族一致
抽象工厂不只是帮你创建对象。
它更重要的是:
保证创建出来的是同一套产品族。
使用 APlatformFactory,就不会创建出 B 平台对象。
使用 BPlatformFactory,也不会混入 A 平台对象。
这在车企项目里很重要。
因为不同平台、不同供应商、不同协议栈之间,很多对象不能随便混用。
3. 业务代码不依赖具体实现
VehicleSystem 依赖的是:
cpp
IVehicleFactory
IBrakeController
ISteeringController
IBatteryManager
它不依赖:
cpp
ABrakeController
BBrakeController
ASteeringController
BSteeringController
这会让业务逻辑更稳定。
如果未来新增 C 平台,只需要增加:
text
CBrakeController
CSteeringController
CBatteryManager
CPlatformFactory
原来的 VehicleSystem 不需要改。
4. 测试更方便
测试时可以创建一套 Mock 工厂:
cpp
class MockVehicleFactory : public IVehicleFactory {
public:
std::unique_ptr<IBrakeController> CreateBrakeController() override {
return std::make_unique<MockBrakeController>();
}
std::unique_ptr<ISteeringController> CreateSteeringController() override {
return std::make_unique<MockSteeringController>();
}
std::unique_ptr<IBatteryManager> CreateBatteryManager() override {
return std::make_unique<MockBatteryManager>();
}
};
这样业务系统不用真实底盘、真实电池、真实转向硬件,也能做流程测试。
这就是依赖抽象带来的好处。
八、车企项目里,哪些地方适合抽象工厂?
1. 不同车型平台的一整套对象创建
这是最典型的场景。
比如 A 平台和 B 平台都需要:
- 制动控制器
- 转向控制器
- 动力控制器
- 电池管理器
- 通信对象
这些对象彼此关联,不能随意混用。
这时抽象工厂很适合。
2. 不同供应商的一组设备适配
比如同一套感知系统,要适配不同供应商。
A 供应商可能提供:
- A 摄像头
- A 毫米波雷达
- A 融合模块
B 供应商可能提供:
- B 摄像头
- B 毫米波雷达
- B 融合模块
上层感知流程只希望依赖统一接口。
具体创建哪一套供应商对象,由具体工厂决定。
3. 真车、仿真、回放环境切换
同一套业务流程可能运行在不同环境中。
真实车辆环境:
text
RealCameraSource
RealVehicleStateReader
RealControlOutput
仿真环境:
text
SimulationCameraSource
SimulationVehicleStateReader
SimulationControlOutput
回放环境:
text
ReplayCameraSource
ReplayVehicleStateReader
ReplayControlOutput
这时可以用不同工厂创建不同运行环境下的一整套对象。
业务流程本身不用改。
4. 不同协议栈的一套通信对象创建
比如诊断通信在不同平台上可能不是只换一个 transport。
它可能同时涉及:
- 连接对象
- 编码对象
- 解码对象
- 会话管理对象
- 超时策略对象
如果这些对象必须成套匹配,就适合抽象工厂。
九、抽象工厂和工厂方法有什么区别?
这是最容易混淆的地方。
可以先用一句话区分:
工厂方法创建一个产品。
抽象工厂创建一族产品。
比如工厂方法关注:
text
我要创建一个 Transport。
抽象工厂关注:
text
我要创建一整套 A 平台对象。
再对比一下:
| 对比项 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 关注点 | 单个产品创建 | 一组相关产品创建 |
| 创建对象数量 | 通常是一类产品 | 多类产品 |
| 核心价值 | 隔离单个创建变化 | 保证产品族一致 |
| 扩展方式 | 新增具体产品和具体工厂 | 新增一整套产品族和具体工厂 |
| 典型场景 | 创建不同通信对象 | 创建不同平台的一整套对象 |
所以,如果只是创建一个对象,用工厂方法就够了。
如果要创建一整套彼此匹配的对象,就可以考虑抽象工厂。
十、抽象工厂和简单工厂有什么区别?
简单工厂通常是一个类里写判断:
cpp
if (platform == A) {
return new ABrakeController();
} else if (platform == B) {
return new BBrakeController();
}
它适合类型少、变化少的场景。
抽象工厂则是把每一套产品族都封装成一个具体工厂:
text
APlatformFactory
BPlatformFactory
CPlatformFactory
每个工厂负责创建自己那一整套对象。
它更适合:
- 平台差异明显
- 产品之间需要成套匹配
- 新产品族会继续增加
- 希望业务代码不依赖具体类
所以,抽象工厂不是简单工厂的"高级版"。
它们解决的问题不完全一样。
简单工厂主要是集中创建逻辑。
抽象工厂主要是管理产品族一致性。
十一、抽象工厂和单例模式有什么关系?
有些项目里,平台工厂本身可能会做成单例。
比如:
text
APlatformFactory::GetInstance()
这样做不是不可以。
但要注意两个问题。
抽象工厂解决的是:
一整套对象族怎么创建。
单例解决的是:
某个对象是否只能有一个。
这两个问题不是一回事。
工厂可以是单例,也可以不是。
产品可以是单例,也可以不是。
不要因为用了抽象工厂,就顺手把所有工厂和产品都做成单例。
这会让系统变得过重,也会影响测试。
十二、抽象工厂最容易被滥用在哪里?
1. 只有一个产品族,也强行抽象工厂
如果系统只有一个平台,而且短期不会扩展到其他平台。
这时写成:
text
IVehicleFactory
APlatformFactory
IBrakeController
ABrakeController
ISteeringController
ASteeringController
IBatteryManager
ABatteryManager
可能就有点重。
抽象工厂适合处理真实存在的产品族变化。
如果没有变化点,就不要为了"像设计模式"而强行套用。
2. 产品族边界没想清楚
抽象工厂最怕产品族划分不清。
比如一个工厂里既创建:
- 制动控制器
- 日志中心
- UI 组件
- 数据库对象
- 网络服务
这些对象之间没有明显的产品族关系。
那这个工厂就会变成新的万能工厂。
好的抽象工厂应该有清晰边界:
它创建的是同一平台、同一供应商、同一运行环境或同一产品族下的一组对象。
如果边界不清,抽象工厂就会变成杂物间。
3. 工厂接口越来越胖
抽象工厂一旦不断扩展,接口可能会变成这样:
cpp
class IVehicleFactory {
public:
virtual CreateBrakeController() = 0;
virtual CreateSteeringController() = 0;
virtual CreateBatteryManager() = 0;
virtual CreateCameraSource() = 0;
virtual CreateRadarSource() = 0;
virtual CreateHmiPresenter() = 0;
virtual CreateLogger() = 0;
virtual CreateDatabase() = 0;
};
这时就要警惕。
因为接口太大,会强迫所有具体工厂都实现一堆自己不关心的方法。
这会违反接口隔离原则。
更好的做法可能是拆成多个工厂:
text
IChassisFactory
IPowertrainFactory
IPerceptionFactory
IDiagnosticFactory
每个工厂只负责一组内聚的产品。
4. 新增单个产品很麻烦
抽象工厂有一个典型缺点:
新增一个产品族比较方便,但新增一种产品类别比较麻烦。
比如现在工厂里有:
text
CreateBrakeController()
CreateSteeringController()
CreateBatteryManager()
如果要新增:
text
CreateThermalManager()
那就要修改抽象工厂接口,也要修改所有具体工厂。
所以抽象工厂适合产品族稳定、产品族数量变化的场景。
如果产品种类本身经常变,就要谨慎使用。
十三、工程中更推荐的用法
1. 先确认是不是"一族对象"问题
使用抽象工厂前,先问:
我现在要创建的是一个对象,还是一整套相关对象?
如果只是一个对象,用工厂方法可能更合适。
如果是一整套对象,而且它们之间必须匹配,抽象工厂才更合适。
2. 产品族边界要清楚
抽象工厂最重要的是产品族边界。
比如:
- A 平台对象族
- B 平台对象族
- 真实环境对象族
- 仿真环境对象族
- A 供应商对象族
- B 供应商对象族
边界越清楚,抽象工厂越有价值。
边界越模糊,工厂越容易变成大杂烩。
3. 工厂接口不要太大
工厂接口应该只包含同一组强相关产品。
如果接口开始膨胀,就要拆分。
比如:
text
底盘对象一组
动力对象一组
感知对象一组
诊断对象一组
不要把整个系统所有对象都塞进一个超级工厂。
4. 业务代码只依赖抽象产品
业务代码拿到对象后,应该通过抽象接口使用它。
如果业务代码还要写:
cpp
if (platform == A) {
dynamic_cast<ABrakeController*>(brake)
}
那说明抽象没有设计好。
抽象工厂的价值,正是让业务代码不需要关心具体产品类型。
5. 注意对象所有权
C++ 中,工厂创建对象时要明确谁负责释放。
一般可以用:
cpp
std::unique_ptr
表达"工厂创建对象,并把所有权交给调用方"。
如果对象是共享服务,可以考虑:
cpp
std::shared_ptr
但不要为了省事全部用 shared_ptr。
所有权越模糊,生命周期越难管理。
十四、抽象工厂的优缺点
优点
抽象工厂的优点很明确:
- 可以统一创建一整套相关对象
- 能保证产品族一致
- 业务代码不依赖具体类
- 新增产品族时比较方便
- 有利于平台、供应商、环境切换
- 有利于测试替身整体替换
它适合处理"成套对象创建"的工程问题。
缺点
它的缺点也很明显:
- 类数量会增加
- 结构比工厂方法更重
- 产品族边界不清时容易变成万能工厂
- 工厂接口可能膨胀
- 新增产品类别时改动较大
- 简单场景下容易过度设计
所以,抽象工厂不是"比工厂方法更高级"。
它只是解决的问题更偏"对象族"。
十五、使用抽象工厂前,先问这 6 个问题
1. 我创建的是一类对象,还是一组对象?
如果只是一类对象,先考虑工厂方法。
2. 这组对象之间是否必须匹配?
如果必须同平台、同供应商、同环境,抽象工厂更有价值。
3. 产品族未来是否会增加?
如果未来会新增 C 平台、D 平台、仿真平台,抽象工厂更合适。
4. 产品类别是否稳定?
如果产品类别经常变化,抽象工厂接口会频繁改动,要谨慎。
5. 工厂接口是否会过大?
如果一个工厂要创建太多无关对象,说明边界可能错了。
6. 引入后业务代码是否更清晰?
如果引入后类更多、依赖更绕、收益不明显,就可能是过度设计。
十六、总结
抽象工厂模式解决的核心问题是:
一整套相关对象,应该由同一套工厂统一创建。
它不是为了把创建逻辑写得更复杂。
它真正想解决的是:
- 产品族如何统一创建
- 平台差异如何集中管理
- 相关对象如何保证匹配
- 业务代码如何不依赖具体实现
- 测试环境如何整体替换对象族
一句话概括:
工厂方法关注"创建一个产品",抽象工厂关注"创建一族产品"。
在车企软件里,它很适合这些场景:
- 不同车型平台的一整套对象创建
- 不同供应商的一组设备适配
- 真车、仿真、回放环境切换
- 不同协议栈的一套通信对象创建
- 测试环境下的一整套 Mock 对象替换
但它也不适合所有场景。
如果系统只有一个产品族,或者产品之间没有强关联,就不要为了"看起来像设计模式"而强行抽象工厂。
设计模式真正有价值的地方,不是让代码显得高级,而是让复杂性有地方安放。
如果上一篇工厂方法模式提醒我们:
不要把"能 new 出来"误当成"创建逻辑就应该到处写"。
那么这一篇抽象工厂模式提醒我们:
不要把"一整套相关对象"拆成一堆互不约束的创建逻辑。
如果这篇对你有帮助,欢迎点赞、转发、关注。
我们下一篇继续拆设计模式。