抽象工厂模式:一整套对象族如何统一创建?

上一篇讲了工厂方法模式。

它解决的问题是:

一个对象的创建逻辑,不应该到处散落在业务代码里。

比如诊断服务需要创建不同通信对象:

  • CAN 通信对象
  • DoIP 通信对象
  • 串口通信对象
  • 仿真通信对象

这时可以用工厂方法,把"创建哪个通信对象"这件事从业务流程里拆出去。

但真实工程里,经常不只是创建一个对象。

很多时候,我们要创建的是一整套相关对象

比如同一套车载软件,要适配不同车型平台:

A 平台可能需要:

  • ABrakeController
  • ASteeringController
  • ABatteryManager
  • ACanTransport

B 平台可能需要:

  • BBrakeController
  • BSteeringController
  • BBatteryManager
  • BDoIPTransport

这时问题就变了。

我们不只是关心:

某一个对象怎么创建?

而是关心:

一整套彼此匹配的对象,怎么统一创建?

这就是抽象工厂模式要解决的问题。


一、先从一个车企场景说起

假设我们现在要做一个整车控制系统。

这个系统要支持两个平台:

  • 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 出来"误当成"创建逻辑就应该到处写"。

那么这一篇抽象工厂模式提醒我们:

不要把"一整套相关对象"拆成一堆互不约束的创建逻辑。

如果这篇对你有帮助,欢迎点赞、转发、关注。

我们下一篇继续拆设计模式。

相关推荐
木易 士心8 小时前
深入理解 OKHttp:设计模式、核心机制与架构优势
android·设计模式·架构
likerhood9 小时前
设计模式 · 外观模式(Facade Pattern)
设计模式·外观模式
++==10 小时前
设计模式:单例模式和观察者模式实现方式以及优化
观察者模式·单例模式·设计模式
doubledong199410 小时前
分形世界与设计模式
设计模式
多加点辣也没关系10 小时前
设计模式-访问者模式
设计模式·访问者模式
咖啡八杯10 小时前
GoF设计模式——原型模式
java·后端·设计模式·原型模式
多加点辣也没关系10 小时前
设计模式-状态模式
设计模式·状态模式
多加点辣也没关系10 小时前
设计模式-备忘录模式
设计模式·备忘录模式