工厂方法模式:简单工厂 vs 工厂方法,产品等级结构,C++中的多态工厂实现
工厂方法模式,说白了就是解决「谁来创建对象」这个问题。我刚开始写C++那会儿,总觉得new一个对象多简单,何必搞个工厂出来?直到项目规模膨胀到几十万行代码,每次新增产品都要改一堆if-else,我才意识到——嗯,设计模式这东西,真不是用来装X的。
简单工厂:最朴素的思路
先聊聊简单工厂。它其实不算GoF的23种设计模式之一,但很多项目都在用。简单工厂的核心思想是:把对象的创建逻辑集中到一个地方。
举个例子,假设我们要创建不同类型的日志记录器:
enum class LoggerType {
File,
Console,
Network
};
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
};
class FileLogger : public Logger {
public:
void log(const std::string& msg) override {
// 写入文件
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << msg << std::endl;
}
};
// 简单工厂
class LoggerFactory {
public:
static std::unique_ptr<Logger> createLogger(LoggerType type) {
switch (type) {
case LoggerType::File:
return std::make_unique<FileLogger>();
case LoggerType::Console:
return std::make_unique<ConsoleLogger>();
default:
throw std::invalid_argument("Unknown logger type");
}
}
};
简单工厂的好处很明显:调用方不用关心具体创建细节。但问题也在这里——每次新增产品,你都得改工厂函数。我在一个老项目中见过一个简单工厂,switch-case写了三十多个分支,维护起来简直想哭。
工厂方法模式:把创建权交给子类
工厂方法模式的核心思想是:定义一个创建对象的接口,让子类决定实例化哪个类。说白了,就是把「创建」这个动作延迟到子类。
还是日志记录器的例子,用工厂方法模式改写:
// 抽象工厂
class LoggerFactory {
public:
virtual ~LoggerFactory() = default;
virtual std::unique_ptr<Logger> createLogger() = 0;
};
// 具体工厂
class FileLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() override {
return std::make_unique<FileLogger>();
}
};
class ConsoleLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> createLogger() override {
return std::make_unique<ConsoleLogger>();
}
};
// 使用
void clientCode(LoggerFactory& factory) {
auto logger = factory.createLogger();
logger->log("Hello, Factory Method!");
}
int main() {
FileLoggerFactory fileFactory;
ConsoleLoggerFactory consoleFactory;
clientCode(fileFactory); // 输出到文件
clientCode(consoleFactory); // 输出到控制台
}
看到区别了吗?简单工厂是「一个工厂管所有产品」,工厂方法是「每个产品有自己的工厂」。新增产品时,你只需要新增一个工厂子类,不用改已有代码——这就是开闭原则的体现。
产品等级结构:理解层次关系
聊到工厂方法,就绕不开「产品等级结构」这个概念。我个人的理解是:产品等级结构就是同一类产品的继承体系。
举个例子:
- 抽象产品:Logger(日志记录器接口)
- 具体产品:FileLogger、ConsoleLogger、NetworkLogger
这就是一个产品等级结构。工厂方法模式中,每个具体工厂对应一个具体产品。你想想看,如果产品等级结构很深(比如有抽象层、中间层、具体层),工厂方法也能很好地应对。
我画了一张图,帮你理清关系:
C++中的多态工厂实现
工厂方法模式在C++中,本质上就是利用虚函数实现多态。我习惯用智能指针管理工厂对象的生命周期,避免内存泄漏。
这里给出一个更完整的实现,包含注册机制:
#include <iostream>
#include <memory>
#include <unordered_map>
#include <functional>
// 抽象产品
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};
// 具体产品A
class ProductA : public Product {
public:
void use() override {
std::cout << "Using Product A" << std::endl;
}
};
// 具体产品B
class ProductB : public Product {
public:
void use() override {
std::cout << "Using Product B" << std::endl;
}
};
// 抽象工厂
class Factory {
public:
virtual ~Factory() = default;
virtual std::unique_ptr<Product> create() = 0;
};
// 具体工厂A
class FactoryA : public Factory {
public:
std::unique_ptr<Product> create() override {
return std::make_unique<ProductA>();
}
};
// 具体工厂B
class FactoryB : public Factory {
public:
std::unique_ptr<Product> create() override {
return std::make_unique<ProductB>();
}
};
// 工厂注册器(可选,用于动态扩展)
class FactoryRegistry {
private:
std::unordered_map<std::string, std::function<std::unique_ptr<Factory>()>> registry;
public:
void registerFactory(const std::string& name,
std::function<std::unique_ptr<Factory>()> creator) {
registry[name] = std::move(creator);
}
std::unique_ptr<Factory> getFactory(const std::string& name) {
auto it = registry.find(name);
if (it != registry.end()) {
return it->second();
}
return nullptr;
}
};
int main() {
FactoryRegistry reg;
reg.registerFactory("A", []() { return std::make_unique<FactoryA>(); });
reg.registerFactory("B", []() { return std::make_unique<FactoryB>(); });
auto factory = reg.getFactory("A");
if (factory) {
auto product = factory->create();
product->use();
}
return 0;
}
简单工厂 vs 工厂方法:怎么选?
我整理了一个对比表格,方便你快速决策:
| 对比维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 核心思想 | 一个工厂函数,根据参数创建不同产品 | 每个产品对应一个工厂子类 |
| 开闭原则 | 违反。新增产品需要修改工厂函数 | 符合。新增产品只需新增工厂子类 |
| 代码复杂度 | 低。一个函数搞定 | 中等。需要定义工厂接口和多个子类 |
| 适用场景 | 产品种类少且稳定,不常变化 | 产品种类多,或经常扩展 |
| 维护成本 | 产品增多时,工厂函数变得臃肿 | 产品增多时,新增类即可,不影响已有代码 |
怎么选?我的建议是:如果你确定产品类型不会超过三五个,而且未来不会频繁变动,简单工厂完全够用。但如果你在做一个框架或库,不确定用户会扩展出什么产品,那就用工厂方法——给用户留好扩展点。
最后说一句,工厂方法模式在C++中还有一个变体——模板工厂方法。利用模板参数来指定产品类型,可以减少子类的数量。不过那是另一个话题了,咱们下次再聊。
公众号:蓝海资料掘金营,微信deep3321