1. 工厂方法模式 (Factory Method)
代码示例
#include <iostream>
#include <string>
using namespace std;// ==========================================
// 1. 抽象产品 (Abstract Product)
// 稳定点:无论日志记录到哪里,写入的接口都是 Log(msg)
// ==========================================
class ILogger {
public:virtual ~ILogger() {}virtual void Log(const string& message) = 0;
};// ==========================================
// 2. 抽象工厂 (Abstract Factory)
// 稳定点:我们总是需要一个“创建者”来生产 logger
// ==========================================
class ILoggerFactory {
public:virtual ~ILoggerFactory() {}// 【工厂方法】:把对象的创建延迟到子类中virtual ILogger* CreateLogger() = 0;
};// ==========================================
// 3. 具体产品 (Concrete Products)
// 变化点:具体的日志记录方式不同
// ==========================================class FileLogger : public ILogger {
public:void Log(const string& message) override {cout << " [文件日志] 写入磁盘: " << message << endl;}
};class DatabaseLogger : public ILogger {
public:void Log(const string& message) override {cout << " [数据库日志] 插入 DB 表: " << message << endl;}
};// ==========================================
// 4. 具体工厂 (Concrete Factories)
// 变化点:每个具体产品对应一个具体工厂
// ==========================================// 文件日志工厂:只负责生产 FileLogger
class FileLoggerFactory : public ILoggerFactory {
public:ILogger* CreateLogger() override {// 这里可以包含复杂的初始化逻辑(比如打开文件句柄)return new FileLogger();}
};// 数据库日志工厂:只负责生产 DatabaseLogger
class DatabaseLoggerFactory : public ILoggerFactory {
public:ILogger* CreateLogger() override {// 这里可以包含连接数据库的配置return new DatabaseLogger();}
};// ==========================================
// 客户端代码 (Client)
// ==========================================// 业务逻辑函数:它只依赖抽象工厂和抽象产品
// 它完全不知道 FileLogger 或 DatabaseLogger 的存在
void DoBusinessLogic(ILoggerFactory* factory) {// 1. 通过工厂创建对象 (Create)ILogger* logger = factory->CreateLogger();// 2. 使用对象 (Use)logger->Log("用户登录成功");logger->Log("订单支付完成");delete logger;
}int main() {cout << "=== 场景 1: 这是一个本地单机版程序 ===" << endl;// 决定使用文件日志ILoggerFactory* factoryA = new FileLoggerFactory();DoBusinessLogic(factoryA);cout << "\n=== 场景 2: 系统升级为企业版 ===" << endl;// 决定使用数据库日志ILoggerFactory* factoryB = new DatabaseLoggerFactory();DoBusinessLogic(factoryB);// 清理工厂delete factoryA;delete factoryB;return 0;
}
1. 定义
- 原文: 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
- 解读:
- “创建对象的接口”:即
ILoggerFactory::CreateLogger()。 - “延迟到子类”:父类(接口)不知道要创建谁,只有具体的子类(
FileLoggerFactory)才知道要new FileLogger。
- “创建对象的接口”:即
- 核心价值: 消除代码中大量的
new ConcreteClass(),从而消除对具体类名的依赖。
2. 解决的问题 (Stable vs. Changing)
这里解决的是“对象创建过程”中的耦合。
- 稳定点 (Stable):对象的生产接口 & 对象的使用接口
- “我要一个 Logger”这个需求是不变的。
- “我要用 Logger 写日志”这个行为是不变的。
- 变化点 (Changing):具体对象的类型
- 今天是
FileLogger,明天可能要换成DatabaseLogger,后天可能是CloudLogger。 - 对象的初始化过程可能很复杂(比如连库、鉴权),这些逻辑也是变化的。
- 今天是
3. 代码结构 (Code Structure)
标准的“四大金刚”结构:
- 抽象产品 (
ILogger):定义产品的规范。 - 具体产品 (
FileLogger):实现具体的功能。 - 抽象工厂 (
ILoggerFactory):定义“生产产品”的规范。 - 具体工厂 (
FileLoggerFactory):实现“生产产品”的具体动作(执行new)。
对比“简单工厂模式” (Simple Factory): 如果用简单工厂,通常会写一个
switch(type)语句:if (type == "File") return new FileLogger();else if (type == "DB") return new DatabaseLogger();这种写法虽然简单,但如果要加新类型,就必须修改这个switch语句,违反了开闭原则。
4. 符合哪些设计原则?
- A. 依赖倒置原则 (DIP) —— 极其重要
- 客户端(
DoBusinessLogic)不依赖FileLogger或DatabaseLogger。 - 客户端只依赖
ILogger和ILoggerFactory。 - 这就是所谓的“面向接口编程”。
- 客户端(
- B. 开闭原则 (OCP)
- 对扩展开放: 如果要加一个“网络日志”,只需要新建文件,写一个
NetworkLogger和NetworkLoggerFactory。 - 对修改关闭: 你不需要修改
ILoggerFactory接口,也不需要修改DoBusinessLogic函数,更不需要修改现有的FileLogger类。
- 对扩展开放: 如果要加一个“网络日志”,只需要新建文件,写一个
- C. 单一职责原则 (SRP)
CreateLogger()的逻辑被拆分到了各个具体的 Factory 中。FileLoggerFactory只负责创建文件日志对象,不负责其他的,代码逻辑清晰。
5. 如何扩展?
这是工厂方法模式最优雅的地方。假设我们需要增加一个 “云端日志 (CloudLogger)”:
-
新建具体产品:
class CloudLogger : public ILogger {void Log(const string& msg) override { cout << "上传到阿里云: " << msg << endl; } }; -
新建具体工厂:
class CloudLoggerFactory : public ILoggerFactory {ILogger* CreateLogger() override { return new CloudLogger(); } }; -
客户端使用:
// 原有的 DoBusinessLogic 完全不用改! ILoggerFactory* cloudFactory = new CloudLoggerFactory(); DoBusinessLogic(cloudFactory);
💡 深思考:为什么不直接 new?
很多初学者会问:“为什么非要搞个工厂类这么麻烦?我直接在 main 里 new FileLogger 不行吗?”
回答: 如果你的程序很小,只有 main 函数里用了一次,那确实可以直接 new。 但是,如果你的 FileLogger 需要在系统中的 100 个地方 被创建,而且它的初始化构造函数很复杂(比如要读取配置文件、设置缓冲区大小)。
- 直接 New: 当你想把 FileLogger 换成 DbLogger 时,你需要去这 100 个地方修改代码。
- 用工厂: 你只需要把那个传递给系统的
factory指针换掉(只改 1 处),整个系统 100 个地方生产出来的对象就瞬间全部变了。