1. 工厂模式三部曲从“小作坊”到“产业集团”的演进之路在嵌入式、物联网乃至整个软件开发领域我们每天都在和“对象”打交道。一个传感器驱动、一个通信协议栈、一个硬件抽象层本质上都是一个个需要被创建和管理的对象。当你的代码里充斥着new SensorA()、new ProtocolB()这样的硬编码时有没有感到一丝不安这种不安正是代码耦合带来的“技术债”预警。今天我们不谈高深的理论就从最接地气的“工厂”概念出发掰开揉碎了聊聊简单工厂、工厂方法和抽象工厂这三种模式。它们不是什么银弹而是三位帮你管理对象创建的“车间主任”各有各的脾气和适用场景。搞懂它们下次在MCU上写驱动在FPGA里做模块化设计或者在云端部署微服务时你就能像搭积木一样优雅地组织你的代码让扩展和维护不再是噩梦。2. 设计模式与工厂家族为什么我们需要“制造”对象在深入工厂模式之前我们得先明白一个根本问题为什么我们不直接new一个对象而要绕个弯子通过“工厂”来创建这就像你饿了是直接去后厨自己动手炒菜直接new还是告诉服务员你想吃什么调用工厂方法显然后者更符合分工也让后厨你的业务逻辑更容易更换厨师具体实现。2.1 直面代码中的“硬耦合”之痛想象一个典型的嵌入式场景你的主控板需要根据拨码开关的状态初始化不同的显示屏驱动比如OLED和LCD。// 糟糕的写法硬编码耦合度高 void init_display(uint8_t switch_status) { if (switch_status 0) { OLED_Display *disp new OLED_Display(); // 直接依赖具体类 disp-init(); } else if (switch_status 1) { LCD_Display *disp new LCD_Display(); // 直接依赖另一个具体类 disp-init(); } // 如果想加一个TFT屏就得来修改这个函数增加一个else if }这段代码的问题一目了然违反开闭原则对扩展开放对修改关闭。增加新屏幕类型必须修改这个函数风险高。客户端负担重调用者init_display函数必须知道所有具体显示类的名字和创建细节。不利于单元测试如果你想测试init_display的逻辑而不依赖真实的硬件驱动你会非常困难。工厂模式的核心使命就是将对象的创建与使用分离。使用方只关心“我需要一个能显示的东西”而不关心它是OLED还是LCD更不关心它内部如何初始化。这个“能显示的东西”就是抽象产品而决定给你OLED还是LCD的那个角色就是工厂。2.2 工厂模式的三大核心角色无论哪种工厂模式都离不开以下几个核心角色的协作抽象产品定义了产品家族的通用接口。比如一个Display接口有init(),showText(),clear()等方法。具体产品实现了抽象产品接口的具体类。比如OLED_Display和LCD_Display。创建者负责创建产品的对象。在不同模式中它的形态和职责有所不同。理解了这些痛点与核心概念我们就可以走进第一个也是最直观的模式简单工厂。3. 简单工厂模式全能但忙碌的“老师傅”简单工厂模式与其说是一种设计模式不如说是一种编程习惯。它像一个车间里唯一的一位老师傅你告诉他你要什么传入一个参数他就能从工具箱里拿出对应的工具给你。3.1 结构解析与嵌入式场景示例它的结构非常简单一个具体工厂类包含一个静态的或实例的创建方法通常通过switch-case或if-else根据传入的参数判断该创建哪个具体产品。一个抽象产品接口。多个具体产品类。让我们用一个物联网终端设备中常见的“数据上传模块”作为例子。设备可能通过4G、NB-IoT或Wi-Fi上传数据。// 抽象产品数据发送器 class DataSender { public: virtual ~DataSender() {} virtual bool send(const std::string data) 0; }; // 具体产品4G发送器 class G4Sender : public DataSender { public: bool send(const std::string data) override { std::cout [4G] 发送数据: data std::endl; // 实际调用4G模组AT指令 return true; } }; // 具体产品NB-IoT发送器 class NBIotSender : public DataSender { public: bool send(const std::string data) override { std::cout [NB-IoT] 发送数据: data std::endl; // 实际调用NB-IoT模组指令 return true; } }; // 具体产品Wi-Fi发送器 class WifiSender : public DataSender { public: bool send(const std::string data) override { std::cout [Wi-Fi] 发送数据: data std::endl; // 实际调用Socket API return true; } }; // 简单工厂那个“老师傅” class SenderFactory { public: enum SenderType { G4, NB_IOT, WIFI }; static DataSender* createSender(SenderType type) { switch (type) { case G4: return new G4Sender(); case NB_IOT: return new NBIotSender(); case WIFI: return new WifiSender(); default: return nullptr; } } };使用起来非常直接int main() { // 根据配置或环境决定使用哪种发送方式 DataSender* sender SenderFactory::createSender(SenderFactory::G4); if (sender) { sender-send(温湿度数据: 25.5C, 60%); delete sender; } return 0; }3.2 优点与致命缺点优点直观易懂客户端调用非常简单只需知道工厂类和参数。初步解耦客户端代码main函数不再依赖G4Sender等具体类只依赖DataSender和SenderFactory。致命缺点为什么它不算23种经典模式之一违反开闭原则这是最核心的问题。如果我们要新增一个LoraSender必须去修改SenderFactory::createSender方法中的switch语句。这意味着工厂类对修改是开放的而不是封闭的。工厂职责过重随着产品种类增多这个工厂方法会变得异常臃肿判断逻辑复杂。难以扩展产品族如果产品之间存在关联比如需要创建一套匹配的“发送器”和“接收器”简单工厂就力不从心了。实操心得简单工厂适用于产品类型极少比如不超过5种且几乎确定未来不会频繁增加新产品的场景。它更像一种过渡方案在快速原型开发或小型工具中可以使用。但在中大型项目尤其是嵌入式系统这种生命周期长、需求易变的领域直接使用简单工厂会为后期维护埋下大坑。4. 工厂方法模式各司其职的“专业车间”为了解决简单工厂的“全能老师傅”负担过重问题工厂方法模式选择了一种分权策略为每一种产品建立一个专门的工厂。就像一个大公司有专门生产螺丝的车间也有专门生产齿轮的车间。4.1 模式结构与C实现精讲工厂方法模式定义了一个用于创建对象的接口抽象工厂但让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类。角色清晰划分抽象工厂声明工厂方法返回一个抽象产品。具体工厂实现工厂方法返回一个具体产品。抽象产品。具体产品。继续用数据发送器的例子我们进行改造// 抽象产品不变 class DataSender { public: virtual ~DataSender() {} virtual bool send(const std::string data) 0; }; // 具体产品不变 class G4Sender : public DataSender { /* ... */ }; class NBIotSender : public DataSender { /* ... */ }; class WifiSender : public DataSender { /* ... */ }; // 抽象工厂 class SenderFactory { public: virtual ~SenderFactory() {} virtual DataSender* createSender() 0; // 工厂方法 }; // 具体工厂专门生产G4发送器 class G4SenderFactory : public SenderFactory { public: DataSender* createSender() override { return new G4Sender(); // 专注于创建G4Sender } }; // 具体工厂专门生产NB-IoT发送器 class NBIotSenderFactory : public SenderFactory { public: DataSender* createSender() override { return new NBIotSender(); } }; // 具体工厂专门生产Wi-Fi发送器 class WifiSenderFactory : public SenderFactory { public: DataSender* createSender() override { return new WifiSender(); } };使用方式发生了变化int main() { // 假设我们从配置文件中读到当前应该使用 G4 std::string config load_config(network_type); SenderFactory* factory nullptr; // 根据配置创建具体的工厂 if (config G4) { factory new G4SenderFactory(); } else if (config NB_IOT) { factory new NBIotSenderFactory(); } else if (config WIFI) { factory new WifiSenderFactory(); } if (factory) { // 通过工厂创建产品客户端完全不知道具体产品类 DataSender* sender factory-createSender(); sender-send(设备状态: OK); delete sender; delete factory; } return 0; }4.2 优势解读与适用场景核心优势完全符合开闭原则现在要增加一个LoraSender我们只需要做两件事创建LoraSender类创建LoraSenderFactory类。完全不需要修改任何现有的工厂和客户端代码。客户端代码中那个if-else虽然需要修改但那是“对象创建”的配置逻辑而非“对象创建”本身通常可以通过反射、依赖注入容器等更高级的方式进一步优化。单一职责每个工厂只负责创建一种产品职责清晰。更强的可扩展性可以轻松地与其它模式结合例如使用配置文件加反射来动态加载工厂类实现插件化架构。适用场景框架设计一个框架需要定义标准接口但将具体实现的创建留给框架的使用者。比如嵌入式RTOS中的任务创建钩子。跨平台开发你的应用需要在Linux和FreeRTOS上运行它们对“文件系统”、“线程”的实现不同。你可以为每个平台定义一套具体的工厂和产品。复杂对象创建当一个对象的创建过程非常复杂涉及多个步骤或依赖其他组件将其封装在一个专门的工厂里是明智的。注意事项工厂方法模式的一个副作用是“类爆炸”。每个具体产品都对应一个具体工厂。如果产品种类非常多比如有几十种传感器就会产生大量的工厂类。这时需要权衡或者考虑使用“原型模式”或“抽象工厂模式”来管理。5. 抽象工厂模式提供“全家桶”的“品牌商”工厂方法模式解决了单一产品族的创建问题。但现实中对象往往不是孤立存在的它们需要成套使用。比如在UI系统中Button、TextBox、Menu需要风格一致在跨平台开发中LinuxButton和LinuxMenu是一套WindowsButton和WindowsMenu是另一套。抽象工厂模式就是为了创建一系列相关或依赖对象而生的它提供一个接口用于创建整个产品家族。5.1 理解产品族与产品等级这是理解抽象工厂的关键。产品等级结构产品的继承结构。例如抽象按钮Button其具体产品有WindowsButton和MacButton。它们属于同一产品等级。产品族指由同一个工厂生产的位于不同产品等级结构中的一组产品。例如WindowsFactory生产的WindowsButton、WindowsTextBox、WindowsMenu就构成了一个“Windows风格”产品族。抽象工厂模式就是围绕“产品族”这个概念展开的。5.2 模式深度解析与复杂场景应用让我们构建一个更贴近硬件的例子一个智能家居网关需要连接不同品牌的设备比如A品牌和B品牌。每个品牌都提供一套设备包括智能灯Light和智能插座Socket。我们希望网关程序能方便地切换整套设备品牌。// 抽象产品族1灯 class Light { public: virtual ~Light() {} virtual void turnOn() 0; virtual void turnOff() 0; virtual void setBrightness(int level) 0; }; // 抽象产品族2插座 class Socket { public: virtual ~Socket() {} virtual void powerOn() 0; virtual void powerOff() 0; virtual int getCurrentPower() 0; }; // A品牌的具体产品 class BrandALight : public Light { public: void turnOn() override { std::cout A品牌灯 - 打开柔和白光 std::endl; } void turnOff() override { std::cout A品牌灯 - 关闭 std::endl; } void setBrightness(int level) override { std::cout A品牌灯 - 设置亮度为 level % std::endl; } }; class BrandASocket : public Socket { public: void powerOn() override { std::cout A品牌插座 - 通电 std::endl; } void powerOff() override { std::cout A品牌插座 - 断电 std::endl; } int getCurrentPower() override { std::cout A品牌插座 - 读取功耗: 50W std::endl; return 50; } }; // B品牌的具体产品 class BrandBLight : public Light { public: void turnOn() override { std::cout B品牌灯 - 打开RGB流光 std::endl; } void turnOff() override { std::cout B品牌灯 - 关闭 std::endl; } void setBrightness(int level) override { std::cout B品牌灯 - 设置亮度为 level 并调整色温 std::endl; } }; class BrandBSocket : public Socket { public: void powerOn() override { std::cout B品牌插座 - 通电带安全自检 std::endl; } void powerOff() override { std::cout B品牌插座 - 断电 std::endl; } int getCurrentPower() override { std::cout B品牌插座 - 读取功耗并通过Wi-Fi上报: 65W std::endl; return 65; } }; // 抽象工厂能生产一整套智能家居设备 class SmartHomeFactory { public: virtual ~SmartHomeFactory() {} virtual Light* createLight() 0; virtual Socket* createSocket() 0; // 未来可以轻松扩展比如增加 createThermostat() 创建恒温器 }; // A品牌的具体工厂 class BrandAFactory : public SmartHomeFactory { public: Light* createLight() override { return new BrandALight(); // 生产A品牌的灯 } Socket* createSocket() override { return new BrandASocket(); // 生产A品牌的插座 } }; // B品牌的具体工厂 class BrandBFactory : public SmartHomeFactory { public: Light* createLight() override { return new BrandBLight(); // 生产B品牌的灯 } Socket* createSocket() override { return new BrandBSocket(); // 生产B品牌的插座 } };客户端代码使用抽象工厂// 网关应用类 class HomeGateway { private: Light* livingRoomLight; Socket* kitchenSocket; SmartHomeFactory* factory; // 持有一个抽象工厂 public: HomeGateway(SmartHomeFactory* factory) : factory(factory) { // 使用同一个工厂创建一套协调的设备 livingRoomLight factory-createLight(); kitchenSocket factory-createSocket(); } void runDailyScene() { std::cout 启动回家场景 std::endl; livingRoomLight-turnOn(); livingRoomLight-setBrightness(70); kitchenSocket-powerOn(); int power kitchenSocket-getCurrentPower(); std::cout 当前厨房插座功耗: power W std::endl; } ~HomeGateway() { delete livingRoomLight; delete kitchenSocket; delete factory; } }; int main() { // 通过一个简单的配置决定使用哪个品牌的全套设备 std::string brand load_config(preferred_brand); // 假设返回 BrandA SmartHomeFactory* factory nullptr; if (brand BrandA) { factory new BrandAFactory(); } else if (brand BrandB) { factory new BrandBFactory(); } else { // 默认或错误处理 factory new BrandAFactory(); } // 网关初始化时传入工厂之后所有设备都来自这个品牌族 HomeGateway gateway(factory); gateway.runDailyScene(); return 0; }5.3 优缺点与实战权衡优点保证产品族内的兼容性这是最大的优点。BrandAFactory生产的Light和Socket在设计上就是能完美协同工作的比如使用相同的通信协议、数据格式。客户端无需关心具体品牌只需使用抽象接口且能确保用到的一套设备是兼容的。切换产品族非常容易要换一套品牌只需在程序入口处更换一个具体的工厂对象即可比如把new BrandAFactory()改成new BrandBFactory()整个系统使用的设备品牌就全变了。符合开闭原则针对产品族增加一个新的产品族比如C品牌非常容易只需新增BrandCFactory、BrandCLight、BrandCSocket而无需修改任何现有工厂和客户端业务逻辑。缺点难以支持新种类的产品这是抽象工厂模式最大的局限。如果产品族需要增加一个新产品种类比如除了Light和Socket现在还需要Thermostat恒温器那么就需要修改SmartHomeFactory抽象接口以及所有已有的具体工厂BrandAFactory,BrandBFactory这违反了开闭原则。类复杂度高由于涉及多个产品等级和产品族系统中类的数量会成对增加理解和管理起来更复杂。实战心得抽象工厂模式是“重量级”的模式不要轻率使用。它的最佳使用场景是系统需要一系列相互关联、相互依赖的产品对象且这些产品族有多个不同的实现系统需要能在不同产品族之间方便地切换。在嵌入式领域典型的应用包括跨平台GUI库、不同芯片厂商的HAL驱动库适配、通信协议栈的不同实现如LwIP vs. FreeRTOSTCP等。在决定使用前务必确认你的产品族结构是否稳定未来新增产品种类的可能性大不大。6. 三种模式对比与选型指南为了更直观地理解三者的区别我们将其核心特性总结如下表特性维度简单工厂模式工厂方法模式抽象工厂模式核心角色一个具体工厂类一个抽象工厂 多个具体工厂一个抽象工厂多方法 多个具体工厂创建目标一种产品多个具体类型一种产品一个具体类型一系列相关产品一个产品族扩展性差。新增产品需修改工厂类。好。新增产品时新增具体工厂即可。对产品族扩展好。新增产品族容易。对产品种类扩展差。新增产品种类需修改所有工厂。复杂度最低中等最高符合开闭原则不符合符合部分符合对扩展产品族开放对扩展产品种类关闭适用场景产品类型极少且固定的小型工具或原型。不明确运行时对象类型或希望将创建逻辑延迟到子类。框架设计、跨平台应用。需要创建一整套相互关联的产品。UI主题切换、跨平台套件、不同品牌的设备驱动集成。选型决策流程图是否需要创建“一系列”而不仅仅是“一个”对象是- 考虑抽象工厂模式。否- 进入下一步。产品的创建过程是否复杂或者未来是否需要灵活支持多种不同的具体产品是且产品种类可能频繁增加- 使用工厂方法模式。否产品类型非常固定且简单- 可以考虑简单工厂模式作为快捷实现。一个综合案例思考假设你在开发一个嵌入式设备固件需要支持多种传感器温度、湿度和多种通信方式4G、LoRa。你该如何设计方案A错误用一个巨大的简单工厂根据枚举类型创建所有传感器和通信模块。问题违反开闭原则工厂类臃肿传感器和通信模块的耦合被硬编码在工厂里。方案B工厂方法为Sensor和Communicator分别建立工厂方法体系。TemperatureSensorFactory,HumiditySensorFactory,G4CommFactory,LoRaCommFactory。这解决了各自内部的扩展问题。方案C抽象工厂如果你发现设备有“户外工业版”用工业级温湿度传感器LoRa和“室内消费版”用消费级传感器Wi-Fi两种固定套装那么可以定义IndustrialFactory和ConsumerFactory。每个工厂负责创建对应套装里的传感器和通信器。这保证了套装内设备的兼容性切换版本只需换一个工厂。显然方案C在套装产品场景下更优。但在传感器和通信器可以任意组合的通用场景下方案B的灵活性更高。7. 在嵌入式与物联网开发中的实践与避坑理论最终要服务于实践。在资源受限、强实时、高可靠的嵌入式与物联网领域应用设计模式时需要一些特别的考量。7.1 内存管理与生命周期控制在标准的C示例中我们使用了new/delete。但在很多嵌入式环境如无操作系统或使用FreeRTOS的MCU动态内存分配是危险的可能导致内存碎片或分配失败。解决方案静态分配与对象池// 使用 placement new 和预分配的内存池 class StaticG4SenderFactory : public SenderFactory { private: static uint8_t g4SenderMemory[sizeof(G4Sender)]; // 预分配内存 public: DataSender* createSender() override { // 在预分配的内存上构造对象 return new (g4SenderMemory) G4Sender(); } void destroySender(DataSender* sender) { if (sender) { sender-~DataSender(); // 显式调用析构函数 // 内存不释放等待复用 } } };或者更常见的做法是在初始化阶段就创建好所有可能需要的对象工厂只返回这些对象的指针或引用。7.2 使用C语言实现工厂模式很多嵌入式项目仍使用C语言。工厂模式的思想同样可以应用。// 抽象产品函数指针结构体 typedef struct { bool (*send)(void* self, const char* data); void (*destroy)(void* self); } DataSenderVTable; typedef struct { const DataSenderVTable* vptr; } DataSender; // 具体产品 typedef struct { DataSender base; int g4_module_fd; // 具体设备的句柄 } G4Sender; bool G4Sender_send(void* self, const char* data) { G4Sender* sender (G4Sender*)self; // 通过g4_module_fd发送数据 printf([C-G4] Sending: %s\n, data); return true; } void G4Sender_destroy(void* self) { free(self); } static const DataSenderVTable G4Sender_vtable { .send G4Sender_send, .destroy G4Sender_destroy }; // 工厂函数 DataSender* create_g4_sender() { G4Sender* sender (G4Sender*)malloc(sizeof(G4Sender)); if (sender) { sender-base.vptr G4Sender_vtable; sender-g4_module_fd open_g4_device(); // 初始化硬件 } return (DataSender*)sender; }通过函数指针表模拟虚函数表实现了多态。工厂函数create_g4_sender返回抽象类型DataSender*。7.3 常见问题排查与调试技巧问题工厂返回了空指针或错误类型对象。排查检查具体工厂的create方法是否正确返回了new的对象。在C中确保构造函数没有抛出异常。在C中检查内存分配是否成功。技巧在工厂的创建方法中加入健壮的日志输出记录创建了哪个具体产品。在调试阶段可以使用一个“调试工厂”它记录所有创建请求。问题抽象工厂模式下新增一个产品种类如Thermostat导致需要修改大量代码。反思这恰恰说明当初选择抽象工厂时对产品族的稳定性判断有误。如果产品种类经常变化或许工厂方法模式为每个产品种类单独建工厂更合适或者重新评估设计看是否能通过更泛化的接口来容纳新产品。缓解如果必须用抽象工厂且预见变化可以尝试使用“参数化工厂方法”在抽象工厂中提供一个createProduct(const std::string type)的方法但这会削弱类型安全性。问题在资源紧张的MCU上多态带来的虚函数表开销是否值得权衡虚函数调用确实比直接函数调用多一次间接寻址。但对于复杂的、需要灵活配置的系统这点开销换取代码的清晰度和可维护性通常是值得的。对于性能极度敏感的核心中断服务程序应避免在此路径上使用工厂模式创建对象。优化如果确实担心开销可以考虑使用“编译时多态”C模板但这会降低运行时灵活性。或者将工厂模式用于系统初始化阶段运行时只使用创建好的对象指针。工厂模式不是教条而是一套解决问题的思路。在嵌入式开发中理解其精髓——“依赖抽象而非具体”——比生搬硬套类图更重要。当你面对那些需要根据配置、环境或状态来创建不同对象的场景时想想这三位“车间主任”或许就能找到让代码更清晰、更健壮的那把钥匙。
工厂模式详解:从简单工厂到抽象工厂的嵌入式实践
发布时间:2026/6/6 12:58:37
1. 工厂模式三部曲从“小作坊”到“产业集团”的演进之路在嵌入式、物联网乃至整个软件开发领域我们每天都在和“对象”打交道。一个传感器驱动、一个通信协议栈、一个硬件抽象层本质上都是一个个需要被创建和管理的对象。当你的代码里充斥着new SensorA()、new ProtocolB()这样的硬编码时有没有感到一丝不安这种不安正是代码耦合带来的“技术债”预警。今天我们不谈高深的理论就从最接地气的“工厂”概念出发掰开揉碎了聊聊简单工厂、工厂方法和抽象工厂这三种模式。它们不是什么银弹而是三位帮你管理对象创建的“车间主任”各有各的脾气和适用场景。搞懂它们下次在MCU上写驱动在FPGA里做模块化设计或者在云端部署微服务时你就能像搭积木一样优雅地组织你的代码让扩展和维护不再是噩梦。2. 设计模式与工厂家族为什么我们需要“制造”对象在深入工厂模式之前我们得先明白一个根本问题为什么我们不直接new一个对象而要绕个弯子通过“工厂”来创建这就像你饿了是直接去后厨自己动手炒菜直接new还是告诉服务员你想吃什么调用工厂方法显然后者更符合分工也让后厨你的业务逻辑更容易更换厨师具体实现。2.1 直面代码中的“硬耦合”之痛想象一个典型的嵌入式场景你的主控板需要根据拨码开关的状态初始化不同的显示屏驱动比如OLED和LCD。// 糟糕的写法硬编码耦合度高 void init_display(uint8_t switch_status) { if (switch_status 0) { OLED_Display *disp new OLED_Display(); // 直接依赖具体类 disp-init(); } else if (switch_status 1) { LCD_Display *disp new LCD_Display(); // 直接依赖另一个具体类 disp-init(); } // 如果想加一个TFT屏就得来修改这个函数增加一个else if }这段代码的问题一目了然违反开闭原则对扩展开放对修改关闭。增加新屏幕类型必须修改这个函数风险高。客户端负担重调用者init_display函数必须知道所有具体显示类的名字和创建细节。不利于单元测试如果你想测试init_display的逻辑而不依赖真实的硬件驱动你会非常困难。工厂模式的核心使命就是将对象的创建与使用分离。使用方只关心“我需要一个能显示的东西”而不关心它是OLED还是LCD更不关心它内部如何初始化。这个“能显示的东西”就是抽象产品而决定给你OLED还是LCD的那个角色就是工厂。2.2 工厂模式的三大核心角色无论哪种工厂模式都离不开以下几个核心角色的协作抽象产品定义了产品家族的通用接口。比如一个Display接口有init(),showText(),clear()等方法。具体产品实现了抽象产品接口的具体类。比如OLED_Display和LCD_Display。创建者负责创建产品的对象。在不同模式中它的形态和职责有所不同。理解了这些痛点与核心概念我们就可以走进第一个也是最直观的模式简单工厂。3. 简单工厂模式全能但忙碌的“老师傅”简单工厂模式与其说是一种设计模式不如说是一种编程习惯。它像一个车间里唯一的一位老师傅你告诉他你要什么传入一个参数他就能从工具箱里拿出对应的工具给你。3.1 结构解析与嵌入式场景示例它的结构非常简单一个具体工厂类包含一个静态的或实例的创建方法通常通过switch-case或if-else根据传入的参数判断该创建哪个具体产品。一个抽象产品接口。多个具体产品类。让我们用一个物联网终端设备中常见的“数据上传模块”作为例子。设备可能通过4G、NB-IoT或Wi-Fi上传数据。// 抽象产品数据发送器 class DataSender { public: virtual ~DataSender() {} virtual bool send(const std::string data) 0; }; // 具体产品4G发送器 class G4Sender : public DataSender { public: bool send(const std::string data) override { std::cout [4G] 发送数据: data std::endl; // 实际调用4G模组AT指令 return true; } }; // 具体产品NB-IoT发送器 class NBIotSender : public DataSender { public: bool send(const std::string data) override { std::cout [NB-IoT] 发送数据: data std::endl; // 实际调用NB-IoT模组指令 return true; } }; // 具体产品Wi-Fi发送器 class WifiSender : public DataSender { public: bool send(const std::string data) override { std::cout [Wi-Fi] 发送数据: data std::endl; // 实际调用Socket API return true; } }; // 简单工厂那个“老师傅” class SenderFactory { public: enum SenderType { G4, NB_IOT, WIFI }; static DataSender* createSender(SenderType type) { switch (type) { case G4: return new G4Sender(); case NB_IOT: return new NBIotSender(); case WIFI: return new WifiSender(); default: return nullptr; } } };使用起来非常直接int main() { // 根据配置或环境决定使用哪种发送方式 DataSender* sender SenderFactory::createSender(SenderFactory::G4); if (sender) { sender-send(温湿度数据: 25.5C, 60%); delete sender; } return 0; }3.2 优点与致命缺点优点直观易懂客户端调用非常简单只需知道工厂类和参数。初步解耦客户端代码main函数不再依赖G4Sender等具体类只依赖DataSender和SenderFactory。致命缺点为什么它不算23种经典模式之一违反开闭原则这是最核心的问题。如果我们要新增一个LoraSender必须去修改SenderFactory::createSender方法中的switch语句。这意味着工厂类对修改是开放的而不是封闭的。工厂职责过重随着产品种类增多这个工厂方法会变得异常臃肿判断逻辑复杂。难以扩展产品族如果产品之间存在关联比如需要创建一套匹配的“发送器”和“接收器”简单工厂就力不从心了。实操心得简单工厂适用于产品类型极少比如不超过5种且几乎确定未来不会频繁增加新产品的场景。它更像一种过渡方案在快速原型开发或小型工具中可以使用。但在中大型项目尤其是嵌入式系统这种生命周期长、需求易变的领域直接使用简单工厂会为后期维护埋下大坑。4. 工厂方法模式各司其职的“专业车间”为了解决简单工厂的“全能老师傅”负担过重问题工厂方法模式选择了一种分权策略为每一种产品建立一个专门的工厂。就像一个大公司有专门生产螺丝的车间也有专门生产齿轮的车间。4.1 模式结构与C实现精讲工厂方法模式定义了一个用于创建对象的接口抽象工厂但让子类决定实例化哪一个类。工厂方法让类的实例化推迟到子类。角色清晰划分抽象工厂声明工厂方法返回一个抽象产品。具体工厂实现工厂方法返回一个具体产品。抽象产品。具体产品。继续用数据发送器的例子我们进行改造// 抽象产品不变 class DataSender { public: virtual ~DataSender() {} virtual bool send(const std::string data) 0; }; // 具体产品不变 class G4Sender : public DataSender { /* ... */ }; class NBIotSender : public DataSender { /* ... */ }; class WifiSender : public DataSender { /* ... */ }; // 抽象工厂 class SenderFactory { public: virtual ~SenderFactory() {} virtual DataSender* createSender() 0; // 工厂方法 }; // 具体工厂专门生产G4发送器 class G4SenderFactory : public SenderFactory { public: DataSender* createSender() override { return new G4Sender(); // 专注于创建G4Sender } }; // 具体工厂专门生产NB-IoT发送器 class NBIotSenderFactory : public SenderFactory { public: DataSender* createSender() override { return new NBIotSender(); } }; // 具体工厂专门生产Wi-Fi发送器 class WifiSenderFactory : public SenderFactory { public: DataSender* createSender() override { return new WifiSender(); } };使用方式发生了变化int main() { // 假设我们从配置文件中读到当前应该使用 G4 std::string config load_config(network_type); SenderFactory* factory nullptr; // 根据配置创建具体的工厂 if (config G4) { factory new G4SenderFactory(); } else if (config NB_IOT) { factory new NBIotSenderFactory(); } else if (config WIFI) { factory new WifiSenderFactory(); } if (factory) { // 通过工厂创建产品客户端完全不知道具体产品类 DataSender* sender factory-createSender(); sender-send(设备状态: OK); delete sender; delete factory; } return 0; }4.2 优势解读与适用场景核心优势完全符合开闭原则现在要增加一个LoraSender我们只需要做两件事创建LoraSender类创建LoraSenderFactory类。完全不需要修改任何现有的工厂和客户端代码。客户端代码中那个if-else虽然需要修改但那是“对象创建”的配置逻辑而非“对象创建”本身通常可以通过反射、依赖注入容器等更高级的方式进一步优化。单一职责每个工厂只负责创建一种产品职责清晰。更强的可扩展性可以轻松地与其它模式结合例如使用配置文件加反射来动态加载工厂类实现插件化架构。适用场景框架设计一个框架需要定义标准接口但将具体实现的创建留给框架的使用者。比如嵌入式RTOS中的任务创建钩子。跨平台开发你的应用需要在Linux和FreeRTOS上运行它们对“文件系统”、“线程”的实现不同。你可以为每个平台定义一套具体的工厂和产品。复杂对象创建当一个对象的创建过程非常复杂涉及多个步骤或依赖其他组件将其封装在一个专门的工厂里是明智的。注意事项工厂方法模式的一个副作用是“类爆炸”。每个具体产品都对应一个具体工厂。如果产品种类非常多比如有几十种传感器就会产生大量的工厂类。这时需要权衡或者考虑使用“原型模式”或“抽象工厂模式”来管理。5. 抽象工厂模式提供“全家桶”的“品牌商”工厂方法模式解决了单一产品族的创建问题。但现实中对象往往不是孤立存在的它们需要成套使用。比如在UI系统中Button、TextBox、Menu需要风格一致在跨平台开发中LinuxButton和LinuxMenu是一套WindowsButton和WindowsMenu是另一套。抽象工厂模式就是为了创建一系列相关或依赖对象而生的它提供一个接口用于创建整个产品家族。5.1 理解产品族与产品等级这是理解抽象工厂的关键。产品等级结构产品的继承结构。例如抽象按钮Button其具体产品有WindowsButton和MacButton。它们属于同一产品等级。产品族指由同一个工厂生产的位于不同产品等级结构中的一组产品。例如WindowsFactory生产的WindowsButton、WindowsTextBox、WindowsMenu就构成了一个“Windows风格”产品族。抽象工厂模式就是围绕“产品族”这个概念展开的。5.2 模式深度解析与复杂场景应用让我们构建一个更贴近硬件的例子一个智能家居网关需要连接不同品牌的设备比如A品牌和B品牌。每个品牌都提供一套设备包括智能灯Light和智能插座Socket。我们希望网关程序能方便地切换整套设备品牌。// 抽象产品族1灯 class Light { public: virtual ~Light() {} virtual void turnOn() 0; virtual void turnOff() 0; virtual void setBrightness(int level) 0; }; // 抽象产品族2插座 class Socket { public: virtual ~Socket() {} virtual void powerOn() 0; virtual void powerOff() 0; virtual int getCurrentPower() 0; }; // A品牌的具体产品 class BrandALight : public Light { public: void turnOn() override { std::cout A品牌灯 - 打开柔和白光 std::endl; } void turnOff() override { std::cout A品牌灯 - 关闭 std::endl; } void setBrightness(int level) override { std::cout A品牌灯 - 设置亮度为 level % std::endl; } }; class BrandASocket : public Socket { public: void powerOn() override { std::cout A品牌插座 - 通电 std::endl; } void powerOff() override { std::cout A品牌插座 - 断电 std::endl; } int getCurrentPower() override { std::cout A品牌插座 - 读取功耗: 50W std::endl; return 50; } }; // B品牌的具体产品 class BrandBLight : public Light { public: void turnOn() override { std::cout B品牌灯 - 打开RGB流光 std::endl; } void turnOff() override { std::cout B品牌灯 - 关闭 std::endl; } void setBrightness(int level) override { std::cout B品牌灯 - 设置亮度为 level 并调整色温 std::endl; } }; class BrandBSocket : public Socket { public: void powerOn() override { std::cout B品牌插座 - 通电带安全自检 std::endl; } void powerOff() override { std::cout B品牌插座 - 断电 std::endl; } int getCurrentPower() override { std::cout B品牌插座 - 读取功耗并通过Wi-Fi上报: 65W std::endl; return 65; } }; // 抽象工厂能生产一整套智能家居设备 class SmartHomeFactory { public: virtual ~SmartHomeFactory() {} virtual Light* createLight() 0; virtual Socket* createSocket() 0; // 未来可以轻松扩展比如增加 createThermostat() 创建恒温器 }; // A品牌的具体工厂 class BrandAFactory : public SmartHomeFactory { public: Light* createLight() override { return new BrandALight(); // 生产A品牌的灯 } Socket* createSocket() override { return new BrandASocket(); // 生产A品牌的插座 } }; // B品牌的具体工厂 class BrandBFactory : public SmartHomeFactory { public: Light* createLight() override { return new BrandBLight(); // 生产B品牌的灯 } Socket* createSocket() override { return new BrandBSocket(); // 生产B品牌的插座 } };客户端代码使用抽象工厂// 网关应用类 class HomeGateway { private: Light* livingRoomLight; Socket* kitchenSocket; SmartHomeFactory* factory; // 持有一个抽象工厂 public: HomeGateway(SmartHomeFactory* factory) : factory(factory) { // 使用同一个工厂创建一套协调的设备 livingRoomLight factory-createLight(); kitchenSocket factory-createSocket(); } void runDailyScene() { std::cout 启动回家场景 std::endl; livingRoomLight-turnOn(); livingRoomLight-setBrightness(70); kitchenSocket-powerOn(); int power kitchenSocket-getCurrentPower(); std::cout 当前厨房插座功耗: power W std::endl; } ~HomeGateway() { delete livingRoomLight; delete kitchenSocket; delete factory; } }; int main() { // 通过一个简单的配置决定使用哪个品牌的全套设备 std::string brand load_config(preferred_brand); // 假设返回 BrandA SmartHomeFactory* factory nullptr; if (brand BrandA) { factory new BrandAFactory(); } else if (brand BrandB) { factory new BrandBFactory(); } else { // 默认或错误处理 factory new BrandAFactory(); } // 网关初始化时传入工厂之后所有设备都来自这个品牌族 HomeGateway gateway(factory); gateway.runDailyScene(); return 0; }5.3 优缺点与实战权衡优点保证产品族内的兼容性这是最大的优点。BrandAFactory生产的Light和Socket在设计上就是能完美协同工作的比如使用相同的通信协议、数据格式。客户端无需关心具体品牌只需使用抽象接口且能确保用到的一套设备是兼容的。切换产品族非常容易要换一套品牌只需在程序入口处更换一个具体的工厂对象即可比如把new BrandAFactory()改成new BrandBFactory()整个系统使用的设备品牌就全变了。符合开闭原则针对产品族增加一个新的产品族比如C品牌非常容易只需新增BrandCFactory、BrandCLight、BrandCSocket而无需修改任何现有工厂和客户端业务逻辑。缺点难以支持新种类的产品这是抽象工厂模式最大的局限。如果产品族需要增加一个新产品种类比如除了Light和Socket现在还需要Thermostat恒温器那么就需要修改SmartHomeFactory抽象接口以及所有已有的具体工厂BrandAFactory,BrandBFactory这违反了开闭原则。类复杂度高由于涉及多个产品等级和产品族系统中类的数量会成对增加理解和管理起来更复杂。实战心得抽象工厂模式是“重量级”的模式不要轻率使用。它的最佳使用场景是系统需要一系列相互关联、相互依赖的产品对象且这些产品族有多个不同的实现系统需要能在不同产品族之间方便地切换。在嵌入式领域典型的应用包括跨平台GUI库、不同芯片厂商的HAL驱动库适配、通信协议栈的不同实现如LwIP vs. FreeRTOSTCP等。在决定使用前务必确认你的产品族结构是否稳定未来新增产品种类的可能性大不大。6. 三种模式对比与选型指南为了更直观地理解三者的区别我们将其核心特性总结如下表特性维度简单工厂模式工厂方法模式抽象工厂模式核心角色一个具体工厂类一个抽象工厂 多个具体工厂一个抽象工厂多方法 多个具体工厂创建目标一种产品多个具体类型一种产品一个具体类型一系列相关产品一个产品族扩展性差。新增产品需修改工厂类。好。新增产品时新增具体工厂即可。对产品族扩展好。新增产品族容易。对产品种类扩展差。新增产品种类需修改所有工厂。复杂度最低中等最高符合开闭原则不符合符合部分符合对扩展产品族开放对扩展产品种类关闭适用场景产品类型极少且固定的小型工具或原型。不明确运行时对象类型或希望将创建逻辑延迟到子类。框架设计、跨平台应用。需要创建一整套相互关联的产品。UI主题切换、跨平台套件、不同品牌的设备驱动集成。选型决策流程图是否需要创建“一系列”而不仅仅是“一个”对象是- 考虑抽象工厂模式。否- 进入下一步。产品的创建过程是否复杂或者未来是否需要灵活支持多种不同的具体产品是且产品种类可能频繁增加- 使用工厂方法模式。否产品类型非常固定且简单- 可以考虑简单工厂模式作为快捷实现。一个综合案例思考假设你在开发一个嵌入式设备固件需要支持多种传感器温度、湿度和多种通信方式4G、LoRa。你该如何设计方案A错误用一个巨大的简单工厂根据枚举类型创建所有传感器和通信模块。问题违反开闭原则工厂类臃肿传感器和通信模块的耦合被硬编码在工厂里。方案B工厂方法为Sensor和Communicator分别建立工厂方法体系。TemperatureSensorFactory,HumiditySensorFactory,G4CommFactory,LoRaCommFactory。这解决了各自内部的扩展问题。方案C抽象工厂如果你发现设备有“户外工业版”用工业级温湿度传感器LoRa和“室内消费版”用消费级传感器Wi-Fi两种固定套装那么可以定义IndustrialFactory和ConsumerFactory。每个工厂负责创建对应套装里的传感器和通信器。这保证了套装内设备的兼容性切换版本只需换一个工厂。显然方案C在套装产品场景下更优。但在传感器和通信器可以任意组合的通用场景下方案B的灵活性更高。7. 在嵌入式与物联网开发中的实践与避坑理论最终要服务于实践。在资源受限、强实时、高可靠的嵌入式与物联网领域应用设计模式时需要一些特别的考量。7.1 内存管理与生命周期控制在标准的C示例中我们使用了new/delete。但在很多嵌入式环境如无操作系统或使用FreeRTOS的MCU动态内存分配是危险的可能导致内存碎片或分配失败。解决方案静态分配与对象池// 使用 placement new 和预分配的内存池 class StaticG4SenderFactory : public SenderFactory { private: static uint8_t g4SenderMemory[sizeof(G4Sender)]; // 预分配内存 public: DataSender* createSender() override { // 在预分配的内存上构造对象 return new (g4SenderMemory) G4Sender(); } void destroySender(DataSender* sender) { if (sender) { sender-~DataSender(); // 显式调用析构函数 // 内存不释放等待复用 } } };或者更常见的做法是在初始化阶段就创建好所有可能需要的对象工厂只返回这些对象的指针或引用。7.2 使用C语言实现工厂模式很多嵌入式项目仍使用C语言。工厂模式的思想同样可以应用。// 抽象产品函数指针结构体 typedef struct { bool (*send)(void* self, const char* data); void (*destroy)(void* self); } DataSenderVTable; typedef struct { const DataSenderVTable* vptr; } DataSender; // 具体产品 typedef struct { DataSender base; int g4_module_fd; // 具体设备的句柄 } G4Sender; bool G4Sender_send(void* self, const char* data) { G4Sender* sender (G4Sender*)self; // 通过g4_module_fd发送数据 printf([C-G4] Sending: %s\n, data); return true; } void G4Sender_destroy(void* self) { free(self); } static const DataSenderVTable G4Sender_vtable { .send G4Sender_send, .destroy G4Sender_destroy }; // 工厂函数 DataSender* create_g4_sender() { G4Sender* sender (G4Sender*)malloc(sizeof(G4Sender)); if (sender) { sender-base.vptr G4Sender_vtable; sender-g4_module_fd open_g4_device(); // 初始化硬件 } return (DataSender*)sender; }通过函数指针表模拟虚函数表实现了多态。工厂函数create_g4_sender返回抽象类型DataSender*。7.3 常见问题排查与调试技巧问题工厂返回了空指针或错误类型对象。排查检查具体工厂的create方法是否正确返回了new的对象。在C中确保构造函数没有抛出异常。在C中检查内存分配是否成功。技巧在工厂的创建方法中加入健壮的日志输出记录创建了哪个具体产品。在调试阶段可以使用一个“调试工厂”它记录所有创建请求。问题抽象工厂模式下新增一个产品种类如Thermostat导致需要修改大量代码。反思这恰恰说明当初选择抽象工厂时对产品族的稳定性判断有误。如果产品种类经常变化或许工厂方法模式为每个产品种类单独建工厂更合适或者重新评估设计看是否能通过更泛化的接口来容纳新产品。缓解如果必须用抽象工厂且预见变化可以尝试使用“参数化工厂方法”在抽象工厂中提供一个createProduct(const std::string type)的方法但这会削弱类型安全性。问题在资源紧张的MCU上多态带来的虚函数表开销是否值得权衡虚函数调用确实比直接函数调用多一次间接寻址。但对于复杂的、需要灵活配置的系统这点开销换取代码的清晰度和可维护性通常是值得的。对于性能极度敏感的核心中断服务程序应避免在此路径上使用工厂模式创建对象。优化如果确实担心开销可以考虑使用“编译时多态”C模板但这会降低运行时灵活性。或者将工厂模式用于系统初始化阶段运行时只使用创建好的对象指针。工厂模式不是教条而是一套解决问题的思路。在嵌入式开发中理解其精髓——“依赖抽象而非具体”——比生搬硬套类图更重要。当你面对那些需要根据配置、环境或状态来创建不同对象的场景时想想这三位“车间主任”或许就能找到让代码更清晰、更健壮的那把钥匙。