当前位置: 首页 > news >正文

设计模式 (四)

抽象工厂模式(Abstract Factory Pattern)详解

一、核心概念

抽象工厂模式是一种创建型设计模式,其核心思想是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它允许客户端通过抽象接口创建一组产品,而不必关心具体实现类。

二、核心角色
  1. 抽象工厂(Abstract Factory)

    • 定义创建一组产品的抽象方法(如createProductA()createProductB())。
  2. 具体工厂(Concrete Factory)

    • 实现抽象工厂的方法,创建具体产品的实例。
  3. 抽象产品(Abstract Product)

    • 定义产品的接口或抽象类。
  4. 具体产品(Concrete Product)

    • 实现抽象产品接口,由具体工厂创建。
三、代码示例

场景:跨平台UI组件工厂,支持创建Windows和macOS的按钮、文本框等组件。

步骤 1:定义抽象产品

// 抽象按钮
class Button {
public:virtual void paint() = 0;virtual ~Button() = default;
};// 抽象文本框
class TextBox {
public:virtual void render() = 0;virtual ~TextBox() = default;
};

步骤 2:定义具体产品

// Windows按钮
class WindowsButton : public Button {
public:void paint() override {std::cout << "Windows风格按钮" << std::endl;}
};// macOS按钮
class MacOSButton : public Button {
public:void paint() override {std::cout << "macOS风格按钮" << std::endl;}
};// Windows文本框
class WindowsTextBox : public TextBox {
public:void render() override {std::cout << "Windows风格文本框" << std::endl;}
};// macOS文本框
class MacOSTextBox : public TextBox {
public:void render() override {std::cout << "macOS风格文本框" << std::endl;}
};

步骤 3:定义抽象工厂

// 抽象工厂:创建UI组件
class UIComponentFactory {
public:virtual Button* createButton() = 0;virtual TextBox* createTextBox() = 0;virtual ~UIComponentFactory() = default;
};

步骤 4:定义具体工厂

// Windows工厂
class WindowsFactory : public UIComponentFactory {
public:Button* createButton() override {return new WindowsButton();}TextBox* createTextBox() override {return new WindowsTextBox();}
};// macOS工厂
class MacOSFactory : public UIComponentFactory {
public:Button* createButton() override {return new MacOSButton();}TextBox* createTextBox() override {return new MacOSTextBox();}
};

步骤 5:客户端代码

// 客户端:使用抽象工厂创建UI组件
void createUI(UIComponentFactory* factory) {Button* button = factory->createButton();TextBox* textBox = factory->createTextBox();button->paint();textBox->render();delete button;delete textBox;
}int main() {// 根据运行平台选择工厂UIComponentFactory* factory;#ifdef _WIN32factory = new WindowsFactory();#elsefactory = new MacOSFactory();#endifcreateUI(factory);delete factory;return 0;
}
四、抽象工厂与其他工厂模式的对比
维度简单工厂(Simple Factory)工厂方法(Factory Method)抽象工厂(Abstract Factory)
核心思想通过一个工厂类创建所有产品通过子类决定创建哪个具体产品创建一组相关产品,无需指定具体类
工厂结构单个工厂类抽象工厂类 + 多个具体工厂子类抽象工厂接口 + 多个具体工厂实现
产品类型单一产品等级结构单一产品等级结构多个产品等级结构(产品族)
扩展性添加新产品需修改工厂类(违反开闭原则)添加新产品只需新增具体工厂子类添加新产品族需新增具体工厂,修改抽象工厂接口
适用场景产品种类少且稳定产品创建逻辑多变系统需独立于产品创建、组合和表示
五、抽象工厂的优势与适用场景
  1. 优势

    • 隔离具体实现:客户端仅依赖抽象接口,无需关心具体产品类。
    • 产品一致性:确保创建的产品属于同一产品族(如Windows风格的所有组件)。
    • 符合开闭原则:新增产品族只需新增具体工厂,无需修改现有代码。
  2. 适用场景

    • 系统需独立于产品创建、组合和表示时。
    • 产品族需协同工作时(如跨平台UI组件、数据库访问驱动)。
    • 需动态切换产品族时(如运行时根据配置选择不同工厂)。
六、抽象工厂的局限性
  1. 扩展困难

    • 添加新产品需修改抽象工厂接口及其所有子类,违反开闭原则。
    • 解决方案:结合反射或配置文件实现动态扩展。
  2. 复杂度高

    • 相比简单工厂和工厂方法,抽象工厂的类结构更复杂,代码量更大。
七、总结

抽象工厂模式的核心价值在于通过抽象接口封装一组产品的创建逻辑,使系统在产品族级别实现松耦合。它与其他工厂模式的区别在于:

  • 简单工厂:集中所有产品的创建逻辑,扩展性差。
  • 工厂方法:将创建逻辑延迟到子类,支持单一产品的扩展。
  • 抽象工厂:支持创建多个相关产品,强调产品族的一致性。

在实际开发中,抽象工厂广泛应用于框架设计(如Spring的BeanFactory)、游戏开发(如不同风格的角色装备工厂)和企业系统(如多数据库支持)。

依赖注入(Dependency Injection)与反射(Reflection):解耦工厂模式

一、简单工厂模式的痛点

简单工厂模式通过switch-caseif-else判断创建具体产品:

// 简单工厂(存在问题)
class ShapeFactory {
public:static Shape* createShape(const std::string& type) {if (type == "circle") {return new Circle();} else if (type == "rectangle") {return new Rectangle();} else if (type == "triangle") {return new Triangle();}return nullptr;  // 未匹配类型}
};

问题

  1. 违反开闭原则:新增产品需修改工厂类的switch-case
  2. 代码臃肿:产品类型过多时,switch-case会变得冗长且难以维护。
  3. 依赖硬编码:工厂类直接依赖具体产品类,耦合度高。
二、依赖注入(DI):解耦对象创建

依赖注入的核心思想是将对象的创建权交给外部,而非在类内部直接实例化。

1. 构造函数注入
// 抽象产品
class Shape {
public:virtual void draw() = 0;virtual ~Shape() = default;
};// 具体产品
class Circle : public Shape {
public:void draw() override { std::cout << "Circle" << std::endl; }
};class Rectangle : public Shape {
public:void draw() override { std::cout << "Rectangle" << std::endl; }
};// 依赖注入:通过构造函数传入依赖
class ShapeRenderer {
private:Shape* shape;  // 依赖抽象,而非具体类
public:explicit ShapeRenderer(Shape* s) : shape(s) {}  // 构造函数注入void render() { shape->draw(); }
};// 客户端代码(负责创建对象)
int main() {Shape* circle = new Circle();ShapeRenderer renderer(circle);  // 注入依赖renderer.render();  // 输出:Circledelete circle;return 0;
}

优势

  • 客户端控制对象创建,工厂类不再需要switch-case
  • 符合开闭原则:新增产品时,只需修改客户端代码,无需改动工厂。
2. 结合配置文件

将产品类型配置到文件中,运行时读取:

// 配置文件 config.json
{"shapeType": "circle"
}// 工厂类(简化版)
class ShapeFactory {
public:static Shape* createShape(const std::string& configFile) {std::string type = readTypeFromConfig(configFile);  // 从配置读取类型if (type == "circle") return new Circle();if (type == "rectangle") return new Rectangle();return nullptr;}
};

优势

  • 无需修改代码,通过配置文件动态切换产品类型。
三、反射(Reflection):动态创建对象

反射允许程序在运行时获取类型信息并动态创建对象,避免硬编码switch-case

1. C++中的反射实现(简化版)

C++标准库无内置反射,但可通过函数注册表模拟:

#include <unordered_map>
#include <functional>// 产品注册表(反射核心)
class ShapeRegistry {
private:using Creator = std::function<Shape*()>;static std::unordered_map<std::string, Creator> registry;public:// 注册产品创建函数static void registerShape(const std::string& type, Creator creator) {registry[type] = creator;}// 通过类型名创建产品static Shape* createShape(const std::string& type) {auto it = registry.find(type);if (it != registry.end()) {return it->second();  // 调用注册的创建函数}return nullptr;}
};// 注册表实例
std::unordered_map<std::string, ShapeRegistry::Creator> ShapeRegistry::registry;// 注册产品(静态初始化)
struct CircleRegistrar {CircleRegistrar() {ShapeRegistry::registerShape("circle", []() { return new Circle(); });}
} circleRegistrar;  // 全局变量触发注册struct RectangleRegistrar {RectangleRegistrar() {ShapeRegistry::registerShape("rectangle", []() { return new Rectangle(); });}
} rectangleRegistrar;// 客户端使用
int main() {Shape* circle = ShapeRegistry::createShape("circle");  // 动态创建if (circle) {circle->draw();  // 输出:Circledelete circle;}return 0;
}

优势

  • 新增产品只需添加注册器,无需修改工厂类。
  • 实现“零配置”:注册逻辑与产品类绑定,自动完成注册。
2. Java中的反射示例

Java提供内置反射API:

// 工厂类
public class ShapeFactory {public static Shape createShape(String className) {try {// 通过类名获取Class对象Class<?> shapeClass = Class.forName(className);// 创建实例return (Shape) shapeClass.getDeclaredConstructor().newInstance();} catch (Exception e) {e.printStackTrace();return null;}}
}// 客户端使用
public static void main(String[] args) {// 通过全限定类名动态创建对象Shape circle = ShapeFactory.createShape("com.example.Circle");if (circle != null) {circle.draw();}
}

优势

  • 完全消除switch-case,通过字符串类名动态创建对象。
  • 支持运行时动态加载类(如插件系统)。
四、依赖注入与反射的结合

在大型框架(如Spring)中,依赖注入与反射常结合使用:

// Spring风格的依赖注入与反射结合
public class ApplicationContext {private Map<String, BeanDefinition> beanDefinitions;private Map<String, Object> singletonBeans;// 从配置文件加载Bean定义public void loadBeans(String configFile) {// 解析配置文件,注册Bean定义beanDefinitions = parseConfig(configFile);}// 获取Bean(通过反射创建)public Object getBean(String beanName) {if (singletonBeans.containsKey(beanName)) {return singletonBeans.get(beanName);}BeanDefinition definition = beanDefinitions.get(beanName);if (definition == null) {throw new IllegalArgumentException("Bean not found: " + beanName);}// 通过反射创建实例Class<?> clazz = Class.forName(definition.getClassName());Object bean = clazz.getDeclaredConstructor().newInstance();// 注入依赖(递归处理)injectDependencies(bean, definition);// 缓存单例if (definition.isSingleton()) {singletonBeans.put(beanName, bean);}return bean;}
}
五、三种模式对比
模式解决思路优点缺点
简单工厂使用switch-case判断创建逻辑实现简单违反开闭原则,扩展性差
依赖注入将对象创建权交给客户端松耦合,符合开闭原则需要外部管理对象关系
反射运行时动态创建对象彻底消除switch-case,高度灵活性能开销,类型安全风险
六、总结

依赖注入和反射通过以下方式解决简单工厂的switch-case问题:

  1. 依赖注入:将对象创建逻辑移至客户端,工厂类仅提供抽象接口,实现松耦合。
  2. 反射:通过类型注册表或语言内置反射API,动态创建对象,避免硬编码条件判断。

在实际应用中:

  • 中小型项目:推荐依赖注入 + 配置文件,平衡复杂度与灵活性。
  • 大型框架:依赖注入 + 反射,如Spring、.NET的依赖注入容器。
  • C++项目:通过函数注册表模拟反射,实现类型的动态注册与创建。

这些技术共同提升了代码的可维护性和可扩展性,是现代软件开发的核心模式。

反射与配置文件的结合:C++实现方案

一、核心概念

反射允许程序在运行时动态创建对象,而配置文件则提供了一种外部化、可动态修改的参数存储方式。两者结合可实现:

  • 解耦对象创建:通过配置文件指定类名或标识符,而非硬编码在代码中。
  • 动态配置:无需重新编译代码即可修改系统行为。
  • 插件化架构:支持运行时加载外部模块。
二、配置文件的优势
  1. 可维护性

    • 配置与代码分离,修改配置无需重新编译。
  2. 灵活性

    • 支持动态调整参数(如数据库连接、日志级别)。
  3. 安全性

    • 敏感信息(如密钥)可存储在配置文件中,避免硬编码在代码里。
  4. 多环境适配

    • 同一套代码通过不同配置文件适配开发、测试、生产环境。
三、C++中的实现方案
1. 配置文件格式选择
  • JSON:结构化、易解析(需第三方库如nlohmann/json)。
  • YAML:可读性强(需第三方库如yaml-cpp)。
  • INI:简单键值对(标准库即可处理)。

示例JSON配置

{"database": {"type": "MySQL","host": "localhost","port": 3306},"logger": {"type": "FileLogger","level": "INFO","output": "app.log"}
}
2. 反射注册表实现
#include <string>
#include <unordered_map>
#include <functional>
#include <memory>
#include "nlohmann/json.hpp"  // JSON解析库using json = nlohmann::json;// 基类接口
class Database {
public:virtual void connect() = 0;virtual ~Database() = default;
};// 具体实现
class MySQL : public Database {
public:void connect() override { /* 连接MySQL */ }
};class PostgreSQL : public Database {
public:void connect() override { /* 连接PostgreSQL */ }
};// 反射注册表
class DatabaseRegistry {
private:using Creator = std::function<std::unique_ptr<Database>(const json& config)>;static std::unordered_map<std::string, Creator> registry;public:// 注册类型static void registerType(const std::string& type, Creator creator) {registry[type] = creator;}// 通过配置创建对象static std::unique_ptr<Database> create(const json& config) {std::string type = config["type"].get<std::string>();auto it = registry.find(type);if (it != registry.end()) {return it->second(config);  // 传递配置到创建函数}return nullptr;}
};// 初始化注册表
std::unordered_map<std::string, DatabaseRegistry::Creator> DatabaseRegistry::registry;// 自动注册宏
#define REGISTER_DATABASE(type) \struct type##Registrar { \type##Registrar() { \DatabaseRegistry::registerType(#type, [](const json& config) { \auto db = std::make_unique<type>(); \// 从config初始化db... \return db; \}); \} \}; \static type##Registrar type##RegistrarInstance;// 注册具体类型
REGISTER_DATABASE(MySQL)
REGISTER_DATABASE(PostgreSQL)
3. 配置文件解析与使用
// 加载配置文件
json loadConfig(const std::string& path) {std::ifstream file(path);if (!file.is_open()) {throw std::runtime_error("Failed to open config file");}return json::parse(file);
}// 客户端代码
int main() {try {// 加载配置json config = loadConfig("config.json");// 通过反射创建对象auto db = DatabaseRegistry::create(config["database"]);if (db) {db->connect();  // 连接数据库}// 创建日志记录器auto logger = LoggerRegistry::create(config["logger"]);if (logger) {logger->log("System started");}} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}
四、高级应用:插件系统

结合反射与动态库(DLL/so),实现插件化架构:

// 插件接口
class Plugin {
public:virtual std::string getName() const = 0;virtual void execute() = 0;virtual ~Plugin() = default;
};// 插件管理器
class PluginManager {
private:std::vector<std::pair<void*, std::unique_ptr<Plugin>>> plugins;public:// 加载插件void loadPlugin(const std::string& path, const json& config) {void* handle = dlopen(path.c_str(), RTLD_LAZY);  // Linux动态库加载if (!handle) {throw std::runtime_error("Failed to load plugin: " + std::string(dlerror()));}// 获取创建函数指针using CreateFunc = Plugin*(*)(const json&);CreateFunc create = reinterpret_cast<CreateFunc>(dlsym(handle, "createPlugin"));if (!create) {dlclose(handle);throw std::runtime_error("Plugin does not implement createPlugin");}// 创建插件实例plugins.emplace_back(handle, std::unique_ptr<Plugin>(create(config)));}// 卸载所有插件~PluginManager() {for (auto& pair : plugins) {dlclose(pair.first);}}
};
五、C++反射与配置的注意事项
  1. 性能开销

    • 反射调用比直接调用慢,避免在性能敏感场景使用。
  2. 类型安全

    • 配置文件中的类型名需与注册表严格匹配,建议添加验证机制。
  3. 内存管理

    • 使用智能指针管理动态创建的对象,避免内存泄漏。
  4. 错误处理

    • 配置文件解析失败或反射创建失败时,需提供明确的错误信息。
六、总结

反射与配置文件结合的核心价值在于将对象创建逻辑外部化、动态化,使系统更灵活、可扩展。在C++中实现时:

  1. 反射机制:通过函数注册表模拟,支持类型的动态注册与创建。
  2. 配置解析:使用JSON/YAML等格式存储配置,第三方库解析。
  3. 插件系统:结合动态库实现运行时模块加载,进一步提升扩展性。

这种模式广泛应用于游戏引擎(如Unity的组件系统)、企业框架(如Spring的Bean配置)和大型应用(如Chrome的插件架构)。

相关文章:

  • 【力扣 中等 C】79. 单词搜索
  • Java基础(Maven配置)
  • 【Elasticsearch】most_fields、best_fields、cross_fields 的区别与用法
  • JVM调优实战 Day 10:性能指标采集与可视化
  • 单元测试和集成测试的区别
  • 鸿蒙 Scroll 组件深度解析:丝滑滚动交互全场景实现
  • spring中maven缺少包如何重新加载,报错java: 程序包org.springframework.web.reactive.function不存在
  • win10部署本地LLM和AI Agent
  • docker-compose部署nacos
  • 基于Uniapp+SpringBoot+Vue 的在线商城小程序
  • 前端面试专栏-主流框架:15.Vue模板编译与渲染流程
  • 给自己网站增加一个免费的AI助手,纯HTML
  • VScode使用usb转网口远程开发rk3588
  • InfluxDB 3 Core最后值缓存深度实践:毫秒级响应实时数据的核心引擎
  • 分布式电源采集控制装置:山东光伏电站的“智能中枢”
  • 典型工程应用三
  • Python pyserial库【串口通信】全面讲解
  • vue-28(服务器端渲染(SSR)简介及其优势)
  • 桌面小屏幕实战课程:DesktopScreen 16 HTTP
  • 【Go语言-Day 10】深入指针应用:解锁函数“引用传递”与内存分配的秘密