4. 工厂方法模式:模式动机与定义、角色与结构、C++代码实现、与简单工厂的对比
工厂方法模式,说白了就是让子类去决定创建哪个对象。我刚开始接触设计模式时,总觉得简单工厂已经够用了,干嘛还要搞这么复杂?直到有一次在项目中踩了坑,才真正理解它的价值。
4.1 模式动机:为什么需要工厂方法?
先说说简单工厂的问题。你想想看,简单工厂把所有对象的创建逻辑都塞到一个函数里。每次新增产品,都得改那个工厂函数。这违反了开闭原则——对扩展开放,对修改关闭。
我在项目中遇到过这样一个场景:一个日志系统,需要支持文件日志、数据库日志、网络日志。一开始用简单工厂,挺爽的。后来需求变了,要加一个加密日志。我不得不去改那个工厂函数。改完之后,测试团队发现原有的文件日志创建逻辑被我无意中改坏了。嗯,这就是耦合的代价。
工厂方法模式的动机很直接:把对象的创建延迟到子类。这样,新增产品时,你只需要新增一个工厂子类,不用动已有的代码。
4.2 模式定义与角色
工厂方法模式涉及四个角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| Product | 抽象产品 | 定义产品的接口 |
| ConcreteProduct | 具体产品 | 实现产品接口的具体类 |
| Creator | 抽象创建者 | 声明工厂方法,返回Product对象 |
| ConcreteCreator | 具体创建者 | 实现工厂方法,返回具体的ConcreteProduct |
这里有个关键点:Creator类里通常还会包含一些业务逻辑,这些逻辑依赖于工厂方法返回的产品对象。说白了,Creator不关心具体是哪个产品,它只关心产品能干什么。
4.3 核心结构图
下面这张图展示了工厂方法模式的结构。我习惯用这种图来跟团队沟通设计思路,一目了然。
4.4 C++代码实现
来看一个实际的例子。假设我们要开发一个跨平台的UI按钮库。不同操作系统下,按钮的样式不同。我们用工厂方法模式来实现。
// 抽象产品:按钮
class Button {
public:
virtual ~Button() = default;
virtual void render() = 0;
virtual void onClick() = 0;
};
// 具体产品:Windows按钮
class WindowsButton : public Button {
public:
void render() override {
std::cout << "渲染Windows风格按钮" << std::endl;
}
void onClick() override {
std::cout << "Windows按钮点击事件" << std::endl;
}
};
// 具体产品:Linux按钮
class LinuxButton : public Button {
public:
void render() override {
std::cout << "渲染Linux风格按钮" << std::endl;
}
void onClick() override {
std::cout << "Linux按钮点击事件" << std::endl;
}
};
// 抽象创建者
class Dialog {
public:
virtual ~Dialog() = default;
// 工厂方法
virtual std::unique_ptr<Button> createButton() = 0;
// 业务逻辑,依赖工厂方法
void renderWindow() {
auto btn = createButton();
btn->render();
btn->onClick();
}
};
// 具体创建者:Windows对话框
class WindowsDialog : public Dialog {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<WindowsButton>();
}
};
// 具体创建者:Linux对话框
class LinuxDialog : public Dialog {
public:
std::unique_ptr<Button> createButton() override {
return std::make_unique<LinuxButton>();
}
};
std::unique_ptr 或 std::shared_ptr,避免裸指针带来的内存管理问题。C++11之后,智能指针是标配。
4.5 与简单工厂的对比
简单工厂和工厂方法,到底选哪个?我直接给你列个对比表:
| 对比维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 开闭原则 | 违反。新增产品需修改工厂类 | 符合。新增产品只需新增子类 |
| 复杂度 | 低。一个工厂类搞定 | 中等。需要定义工厂接口和子类 |
| 产品种类 | 适合产品种类少且稳定的场景 | 适合产品种类多且频繁扩展的场景 |
| 代码耦合 | 工厂类与所有产品耦合 | 每个工厂子类只与对应产品耦合 |
| 扩展方式 | 修改已有代码 | 新增代码,不修改已有代码 |
简单工厂适合什么场景?我举个例子:你做一个工具类,就生成两三种日志对象,而且确定以后不会再加了。那简单工厂完全够用,别过度设计。
工厂方法适合什么场景?你想想看,如果你在做一个插件系统,第三方开发者要能注册自己的产品。这时候简单工厂就玩不转了。你总不能每次有新的插件,都去改你的核心代码吧?
4.6 什么时候用工厂方法?
总结一下,我个人判断是否使用工厂方法的几个标准:
- 客户端不关心产品创建细节:客户端只需要知道产品能干什么,不需要知道怎么创建的
- 产品类型可能扩展:未来可能会新增产品类型,而且不希望修改已有代码
- 产品创建逻辑复杂:创建产品需要读取配置、连接数据库、或者做一些初始化操作
- 需要解耦:你想让高层模块不依赖低层模块的具体实现
嗯,工厂方法模式就是这么回事。它不复杂,但用好了能让你的代码更灵活、更健壮。记住一点:设计模式是工具,不是目的。别为了用模式而用模式,要在真正需要的时候才出手。