C++设计模式之工厂模式

文章目录
如果觉得本文对您有所帮助,点个赞和关注吧,谢谢!!!你的支持就是我持续更新的最大动力
导论:为何要超越 new?
在C++中,new 运算符是对象创建的基石。然而,在大型、复杂的软件系统中,直接在业务逻辑中散布 new 调用会带来一个隐蔽而深刻的问题——紧密耦合。当客户端代码直接通过 new ConcreteProduct() 的方式创建对象时,它就与 ConcreteProduct 这个具体的实现类产生了编译时依赖。这意味着,一旦 ConcreteProduct 的实现发生变化,或者我们需要替换为另一个实现 AnotherConcreteProduct,所有直接依赖它的客户端代码都可能需要修改和重新编译。这违反了软件设计中的核心原则之一:依赖倒置原则 (Dependency Inversion Principle - DIP),即“高层模块不应依赖于低层模块,二者都应依赖于抽象”。
工厂模式家族正是为了斩断这种不健康的依赖关系而生。它们通过引入一个中介——“工厂”,将对象的创建过程(“怎么做”)与对象的使用过程(“用什么”)彻底分离。客户端不再关心对象的具体创建细节,只向工厂请求一个符合特定抽象接口的对象。如此,系统便获得了前所未有的灵活性与可维护性。
一、 简单工厂模式 (Simple Factory Pattern)
简单工厂模式,虽常被视为一种编程习惯而非正式的设计模式,但其思想是后续工厂模式的基石。它通过一个专门的类来集中管理对象的创建逻辑,客户端只需提供一个参数,即可获得所需的对象实例。
核心意图:提供一个统一的入口点,根据传入的标识符创建并返回不同类型的具体产品实例,隐藏创建逻辑。
1.1 模式结构与C++实现
- 抽象产品 (Abstract Product -
ILogger): 定义所有产品必须实现的公共接口。 - 具体产品 (Concrete Product -
FileLogger,ConsoleLogger): 抽象产品的具体实现。 - 工厂 (Factory -
LoggerFactory): 包含一个静态方法,根据客户端请求创建具体产品。
严谨的C++实现
#include <iostream>
#include <string>
#include <memory>
#include <stdexcept>
// 为了类型安全和可读性,强烈推荐使用枚举类
enum class LoggerType {
File,
Console,
Database // 假设未来扩展
};
// 1. 抽象产品:日志记录器接口
class ILogger {
public:
// 关键:虚析构函数。当通过基类指针删除派生类对象时,
// 必须有虚析构函数才能正确调用派生类的析构函数,防止资源泄漏。
virtual ~ILogger() = default;
virtual void writeLog(const std::string& message) = 0;
};
// 2. 具体产品 A:文件日志记录器
class FileLogger : public ILogger {
public:
void writeLog(const std::string& message) override {
std::cout << "[File Logger]: " << message << std::endl;
}
};
// 3. 具体产品 B:控制台日志记录器
class ConsoleLogger : public ILogger {
public:
void writeLog(const std::string& message) override {
std::cout << "[Console Logger]: " << message << std::endl;
}
};
// 4. 工厂类
class LoggerFactory {
public:
/**
* @brief 创建日志记录器实例的核心静态方法。
*
* @param type 指定需要创建的日志记录器类型。
* @return std::unique_ptr<ILogger> 返回一个指向所创建日志记录器实例的独占所有权智能指针。
* @throws std::invalid_argument 如果传入了不支持的日志记录器类型。
*/
static std::unique_ptr<ILogger> createLogger(LoggerType type) {
switch (type) {
case LoggerType::File:
return std::make_unique<FileLogger>();
case LoggerType::Console:
return std::make_unique<ConsoleLogger>();
// 当需要添加新的Logger时,必须修改这里
// case LoggerType::Database:
// return std::make_unique<DatabaseLogger>();
default:
throw std::invalid_argument("Unsupported logger type provided.");
}
}
};
// 客户端使用示例
void clientCode(LoggerType type) {
try {
std::unique_ptr<ILogger> logger = LoggerFactory::createLogger(type);
logger->writeLog("This is a test message.");
} catch (const std::invalid_argument& e) {
std::cerr << "Error creating logger: " << e.what() << std::endl;
}
}
int main() {
clientCode(LoggerType::File);
clientCode(LoggerType::Console);
return 0;
}
1.2 关键函数深度解析
static std::unique_ptr<ILogger> LoggerFactory::createLogger(LoggerType type)
-
函数作用:
此函数是简单工厂模式的命脉,它扮演着创造者的角色,将如何创建对象的复杂性(new FileLogger()或new ConsoleLogger())从使用者(客户端代码)中完全剥离。客户端从直接的“建造者”转变为一个“指挥者”,只需下达指令(提供type),而无需关心建造细节。 -
使用格式模版:
std::unique_ptr<AbstractProduct> product = Factory::createProduct(Identifier); -
参数含义:
LoggerType type: 一个enum class类型的参数。它作为产品类型的标识符。使用强类型枚举(enum class)而非原始int或std::string,极大地增强了代码的类型安全性和自文档性,防止了无效值的传入。
-
返回值:
std::unique_ptr<ILogger>: 这是现代C++实践的核心。- 返回基类接口
ILogger:这是实现多态和解耦的关键。客户端通过ILogger接口与对象交互,完全不知道也不需要知道其具体类型是FileLogger还是ConsoleLogger。 - 返回
std::unique_ptr:- 所有权语义:
std::unique_ptr明确表达了“独占所有权”的语义。工厂创建了对象,并将该对象的所有权转移给调用者。调用者(客户端)负责该对象的生命周期。 - RAII (Resource Acquisition Is Initialization):当
unique_ptr离开其作用域时,它会自动调用所管理对象的析构函数,彻底杜绝了因忘记delete而导致的内存泄漏。 - 异常安全:如果在对象创建后、返回前发生异常,智能指针的机制能确保已分配的内存被正确释放。
- 所有权语义:
- 错误处理:对于无效的
type,返回nullptr是一种选择,但抛出异常(如std::invalid_argument)通常是更健壮的做法,因为它能更清晰地向调用者传达“契约失败”的信号。
- 返回基类接口
1.3 设计原则与利弊权衡
-
优点:
- 职责分离:完美遵循单一职责原则 (SRP)。工厂类专职于创建,产品类专职于业务,客户端专职于使用。
- 客户端解耦:客户端代码仅依赖于抽象产品接口和工厂类,与具体产品实现完全解耦。
-
缺点:
- 违反开闭原则 (OCP):这是简单工厂模式的致命缺陷。每当系统中需要增加一个新的具体产品(如
DatabaseLogger),就必须修改LoggerFactory类中的switch-case语句。这使得工厂类成为一个极不稳定的“瓶颈”,违背了“对扩展开放,对修改关闭”的原则。 - 工厂类职责过重:随着产品线的增多,工厂类的
create方法会变得异常臃肿,逻辑复杂,成为难以维护的“上帝类”。
- 违反开闭原则 (OCP):这是简单工厂模式的致命缺陷。每当系统中需要增加一个新的具体产品(如
1.4 何时使用?
- 当需要创建的对象较少,且在可预见的未来不会频繁增加时。
- 当客户端不关心对象的创建过程,只希望通过一个简单的标识符获取实例时。
二、 工厂方法模式 (Factory Method Pattern)
工厂方法模式通过引入继承体系,将简单工厂模式中集中的创建逻辑分散到各个子类中,从而优雅地解决了开闭原则的问题。
核心意图:定义一个用于创建对象的接口(工厂方法),但将实际的创建决策延迟到子类中。这使得一个类的实例化延迟到其子类。
2.1 模式结构与C++实现
- 抽象产品 (
ILogger) & 具体产品 (FileLogger,ConsoleLogger): 保持不变。 - 抽象工厂 (Abstract Factory/Creator -
ILoggerFactory): 声明一个抽象的createLogger方法(工厂方法)。 - 具体工厂 (Concrete Factory/Creator -
FileLoggerFactory,ConsoleLoggerFactory): 实现createLogger方法,返回一个具体的的产品实例。
严谨的C++实现
#include <iostream>
#include <string>
#include <memory>
// 抽象产品与具体产品 (同上)
class ILogger {
public:
virtual ~ILogger() = default;
virtual void writeLog(const std::string& message) = 0;
};
class FileLogger : public ILogger {
public:
void writeLog(const std::string& message) override { std::cout << "[File Logger]: " << message << std::endl; }
};
class ConsoleLogger : public ILogger {
public:
void writeLog(const std::string& message) override { std::cout << "[Console Logger]: " << message << std::endl; }
};
// 1. 抽象工厂 (Creator)
class ILoggerFactory {
public:
virtual ~ILoggerFactory() = default;
/**
* @brief 抽象的工厂方法。
* 这是一个“钩子”方法,由子类实现以提供具体的产品实例。
* @return std::unique_ptr<ILogger> 指向创建的日志记录器实例的智能指针。
*/
virtual std::unique_ptr<ILogger> createLogger() const = 0;
// 抽象工厂可以包含依赖于产品的通用业务逻辑
void someOperation(const std::string& data) const {
// 使用工厂方法创建产品
std::unique_ptr<ILogger> logger = this->createLogger();
// 使用产品
logger->writeLog("Operation started with data: " + data);
// ... more logic ...
logger->writeLog("Operation finished.");
}
};
// 2. 具体工厂 A (Concrete Creator)
class FileLoggerFactory : public ILoggerFactory {
public:
std::unique_ptr<ILogger> createLogger() const override {
return std::make_unique<FileLogger>();
}
};
// 3. 具体工厂 B (Concrete Creator)
class ConsoleLoggerFactory : public ILoggerFactory {
public:
std::unique_ptr<ILogger> createLogger() const override {
return std::make_unique<ConsoleLogger>();
}
};
// 客户端代码现在与具体工厂交互
void clientCode(const ILoggerFactory& factory) {
factory.someOperation("Sensitive data processing.");
}
int main() {
FileLoggerFactory fileFactory;
std::cout << "Client: Using FileLoggerFactory." << std::endl;
clientCode(fileFactory);
std::cout << "\nClient: Using ConsoleLoggerFactory." << std::endl;
ConsoleLoggerFactory consoleFactory;
clientCode(consoleFactory);
// 如果需要增加 DatabaseLogger,只需新增 DatabaseLogger 和 DatabaseLoggerFactory 即可
// class DatabaseLogger : public ILogger { ... };
// class DatabaseLoggerFactory : public ILoggerFactory { ... };
// DatabaseLoggerFactory dbFactory;
// clientCode(dbFactory); // 无需修改任何现有代码
return 0;
}
2.2 关键函数深度解析
virtual std::unique_ptr<ILogger> ILoggerFactory::createLogger() const = 0;
-
函数作用:
此函数是工厂方法模式的灵魂——工厂方法。它在抽象基类中被声明为纯虚函数,从而定义了一个契约:任何ILoggerFactory的子类都必须提供一个createLogger的实现。它将创建的决策权从一个中央集权的工厂下放给了各个具体的工厂子类。每个子类只关心如何创建它所对应的那个产品。 -
virtual和= 0:virtual关键字使其成为虚函数,允许在派生类中重写。= 0使其成为纯虚函数,意味着ILoggerFactory本身不可实例化,它只是一个接口定义。 -
const: 将工厂方法标记为const是一个很好的实践(如果创建过程不改变工厂自身状态的话),它表明调用此方法不会修改工厂对象。 -
使用格式模版:
std::unique_ptr<AbstractFactory> factory = std::make_unique<ConcreteFactory>(); std::unique_ptr<AbstractProduct> product = factory->createProduct(); -
参数含义:
- 在经典实现中,此函数通常无参数。因为具体工厂的类型(例如
FileLoggerFactory)已经内含了要创建何种产品的所有信息。 - 变体:参数化工厂方法:在某些场景下,工厂方法也可以接受参数,以创建更定制化的产品。例如
virtual std::unique_ptr<ILogger> createLogger(const std::string& config_path) const = 0;。
- 在经典实现中,此函数通常无参数。因为具体工厂的类型(例如
-
返回值:
std::unique_ptr<ILogger>: 与简单工厂一致,返回指向抽象产品接口的智能指针,以维持客户端与具体产品的解耦。
2.3 设计原则与利弊权衡
-
优点:
- 完美遵循开闭原则 (OCP):当需要添加新产品时,只需创建一个新的具体产品类和与之对应的具体工厂类,完全不需要修改任何现有代码。系统的可扩展性得到了质的飞跃。
- 遵循依赖倒置原则 (DIP):客户端代码依赖于抽象的
ILoggerFactory和ILogger接口,而不是具体的实现。这使得切换产品实现变得轻而易举。 - 职责更加单一:每个具体工厂只负责创建一种具体产品,职责清晰。
-
缺点:
- 类的数量爆炸:每增加一个产品,就需要至少增加一个具体工厂类。这会导致系统中的类的数量成倍增长,增加了代码库的复杂度和管理成本。
2.4 何时使用?
- 当一个类无法预知它需要创建的对象的具体类型时。
- 当一个类希望其子类来指定它所创建的对象时。
- 当你希望将对象的创建和使用解耦,并且未来可能会有新的产品类型加入系统时。
三、 抽象工厂模式 (Abstract Factory Pattern)
抽象工厂模式是所有工厂模式中抽象层次最高的,它处理的是“产品族”的创建问题。一个产品族是位于不同产品等级结构中,功能上相互关联的一组产品。
核心意图:提供一个接口,用于创建一系列相关或相互依赖的对象(一个产品族),而无需指定它们具体的类。
3.1 模式结构与C++实现
- 抽象工厂 (Abstract Factory -
IUIFactory): 声明一组用于创建不同抽象产品的工厂方法。 - 具体工厂 (Concrete Factory -
DarkThemeFactory,LightThemeFactory): 实现抽象工厂接口,创建属于同一个产品族的具体产品。 - 抽象产品 (Abstract Product -
IButton,ITextBox): 为产品族中的每一种产品定义接口。 - 具体产品 (Concrete Product -
DarkButton,DarkTextBox, etc.): 实现抽象产品接口,构成特定的产品族。
严谨的C++实现
#include <iostream>
#include <string>
#include <memory>
// 抽象产品族
// ===================================
// 抽象产品 A:按钮
class IButton {
public:
virtual ~IButton() = default;
virtual void render() const = 0;
};
// 抽象产品 B:文本框
class ITextBox {
public:
virtual ~ITextBox() = default;
virtual void display() const = 0;
};
// 具体产品族 1:暗色主题
// ===================================
class DarkButton : public IButton {
public:
void render() const override { std::cout << "Rendering a [Dark Button]." << std::endl; }
};
class DarkTextBox : public ITextBox {
public:
void display() const override { std::cout << "Displaying a [Dark Text Box]." << std::endl; }
};
// 具体产品族 2:亮色主题
// ===================================
class LightButton : public IButton {
public:
void render() const override { std::cout << "Rendering a [Light Button]." << std::endl; }
};
class LightTextBox : public ITextBox {
public:
void display() const override { std::cout << "Displaying a [Light Text Box]." << std::endl; }
};
// 抽象工厂:定义了创建产品族的接口
// ===================================
class IUIFactory {
public:
virtual ~IUIFactory() = default;
virtual std::unique_ptr<IButton> createButton() const = 0;
virtual std::unique_ptr<ITextBox> createTextBox() const = 0;
};
// 具体工厂 1:创建暗色主题产品族
// ===================================
class DarkThemeFactory : public IUIFactory {
public:
std::unique_ptr<IButton> createButton() const override {
return std::make_unique<DarkButton>();
}
std::unique_ptr<ITextBox> createTextBox() const override {
return std::make_unique<DarkTextBox>();
}
};
// 具体工厂 2:创建亮色主题产品族
// ===================================
class LightThemeFactory : public IUIFactory {
public:
std::unique_ptr<IButton> createButton() const override {
return std::make_unique<LightButton>();
}
std::unique_ptr<ITextBox> createTextBox() const override {
return std::make_unique<LightThemeFactory>();
}
};
// 客户端代码:完全与具体产品和具体工厂解耦
// ===================================
class Application {
private:
std::unique_ptr<IButton> button_;
std::unique_ptr<ITextBox> textbox_;
public:
// 构造函数注入具体工厂的抽象接口
Application(const IUIFactory& factory) {
std::cout << "Application: Building UI..." << std::endl;
button_ = factory.createButton();
textbox_ = factory.createTextBox();
}
void run() const {
button_->render();
textbox_->display();
}
};
int main() {
DarkThemeFactory darkFactory;
Application app_dark(darkFactory);
app_dark.run();
std::cout << "\n--------------------------------\n";
LightThemeFactory lightFactory;
Application app_light(lightFactory);
app_light.run();
return 0;
}
3.2 关键函数深度解析
virtual std::unique_ptr<IButton> IUIFactory::createButton() const = 0;virtual std::unique_ptr<ITextBox> IUIFactory::createTextBox() const = 0;
-
函数作用:
抽象工厂的核心在于它定义了一组相关的工厂方法,而非单个。每个方法负责创建一个产品族中的特定成员。IUIFactory接口承诺,任何实现它的具体工厂都必须能够生产一个按钮 (IButton) 和一个文本框 (ITextBox)。
最关键的保证是一致性:一个具体工厂(如DarkThemeFactory)创建的所有产品(DarkButton,DarkTextBox)都属于同一个产品族(暗色主题),它们在风格和行为上是相互兼容、协调一致的。 -
使用格式模版:
const AbstractFactory& factory = ConcreteFactory1(); // Or get from config std::unique_ptr<AbstractProductA> productA = factory.createProductA(); std::unique_ptr<AbstractProductB> productB = factory.createProductB(); // productA and productB are guaranteed to be compatible. -
参数与返回值:
- 每个创建方法通常无参数,返回对应抽象产品接口的智能指针。逻辑与工厂方法模式中的单个方法类似。
3.3 设计原则与利弊权衡
-
优点:
- 产品族一致性:强制保证了由一个工厂创建出来的所有产品都是相互匹配的。客户端代码可以放心地将它们组合使用。
- 高度解耦:客户端代码与具体的产品实现完全隔离。切换整个产品族(例如从暗色主题换到亮色主题)只需要在程序启动时实例化一个不同的具体工厂对象即可,业务逻辑代码无需任何改动。
- 遵循开闭原则(对于扩展产品族):增加一个新的产品族(例如“高对比度主题”)非常容易,只需添加新的具体产品类和一个新的具体工厂类即可。
-
缺点:
- 违反开闭原则(对于扩展产品种类):这是抽象工厂模式最广为人知的权衡。如果想在产品族中增加一个新种类的产品(例如,增加
ICheckbox),就必须修改抽象工厂IUIFactory的接口,进而导致所有实现了该接口的具体工厂类都必须进行修改。这在大型系统中可能是灾难性的。
- 违反开闭原则(对于扩展产品种类):这是抽象工厂模式最广为人知的权衡。如果想在产品族中增加一个新种类的产品(例如,增加
3.4 何时使用?
- 当你的系统需要独立于其产品的创建、组合和表示时。
- 当你的系统需要配置为使用多个产品族中的某一个时。
- 当你需要提供一个产品类库,但只想暴露它们的接口而不是实现时。
- 当你希望强制使用者使用来自同一个产品族的产品时。
四、 模式总结与对比
| 特性 / 模式 | 简单工厂模式 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|---|
| 解决问题 | 封装单个对象的创建 | 为单个对象创建提供可扩展的框架 | 创建**一整套(产品族)**的相互关联的对象 |
| 抽象粒度 | 类级(整个工厂是一个类) | 对象级(每个工厂子类对象负责创建) | 接口级(定义创建产品族的接口) |
| OCP 应用 | 违反:增加新产品需修改工厂 | 遵循:增加新产品只需扩展新类 | 部分遵循:扩展新产品族容易,扩展新产品种类困难 |
| 核心思想 | 集中化 | 延迟化/委托化 | 族系化/配套化 |
| 现实世界类比 | 一台多功能饮料机,按按钮出不同饮料 | 麦当劳加盟体系,每个加盟店(具体工厂)可以决定是否推出特色汉堡(具体产品) | 宜家家居,选择“斯堪的纳维亚”风格(具体工厂),就能买到配套的沙发、桌子、灯具(产品族) |
五、结语
工厂模式家族为我们提供了一套从简单到复杂的、用于管理对象创建的强大工具集。它们的核心价值在于控制耦合,通过抽象来隔离变化,从而构建出更具弹性、可维护性和可扩展性的软件系统。
- 简单工厂是快速实现创建逻辑分离的实用工具。
- 工厂方法是遵循开闭原则、实现可扩展创建框架的典范。
- 抽象工厂则是构建需要保证产品兼容性的复杂系统的基石。
作为一名C++开发者,深刻理解每种模式的意图、结构、利弊以及它们与SOLID原则的内在联系,是编写出高质量、高内聚、低耦合代码的关键。在实际项目中,没有最优的模式,只有最适合当前问题上下文和未来演化方向的模式。精通它们,意味着你拥有了在对象创建的复杂棋局中,运筹帷幄、游刃有余的能力。
如果觉得本文对您有所帮助,点个赞和关注吧,谢谢!!!你的支持就是我持续更新的最大动力
更多推荐



所有评论(0)