工厂方法模式:让对象的创建更灵活
工厂方法模式,说白了就是「把创建对象这件事,交给子类去决定」。
我刚开始接触设计模式时,觉得简单工厂就够用了。直到有一次,我在做一个跨平台的消息推送系统——Android、iOS、Web 各一套实现。简单工厂里塞满了 if-else,每次加一个新平台,就要改工厂类。改着改着,我意识到:这违反了开闭原则。嗯,工厂方法模式就是用来解决这个问题的。
模式的核心思想
工厂方法模式定义了一个创建对象的接口,但让子类决定实例化哪个类。它把对象的实例化延迟到子类。
你想想看,这样做的好处是什么?
- 解耦:客户端不需要知道具体产品类名
- 扩展性:新增产品时,不需要修改现有工厂接口
- 符合开闭原则:对扩展开放,对修改关闭
核心角色:
- Product(产品接口):定义产品的公共接口
- ConcreteProduct(具体产品):实现产品接口的具体类
- Creator(工厂接口):声明工厂方法,返回产品对象
- ConcreteCreator(具体工厂):实现工厂方法,创建具体产品
模式结构图
先看一张图,把整体脉络理清楚:
Java 代码示例
先看 Java 的实现。我个人习惯把产品接口定义得尽量简洁:
// 1. 产品接口
public interface Logger {
void log(String message);
}
// 2. 具体产品
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("写入文件日志: " + message);
}
}
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("控制台日志: " + message);
}
}
// 3. 工厂接口
public interface LoggerFactory {
Logger createLogger();
}
// 4. 具体工厂
public class FileLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new FileLogger();
}
}
public class ConsoleLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}
// 5. 客户端使用
public class Client {
public static void main(String[] args) {
LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.log("系统启动成功");
}
}
我的经验:实际项目中,工厂方法经常配合配置文件使用。比如读取一个 config.properties,根据配置项决定用哪个工厂。这样切换实现时,改配置文件就行,代码不用动。
C++ 代码示例
C++ 的实现思路一样,但要注意内存管理。我记得有一次线上崩溃,就是因为工厂返回的指针没有正确释放:
#include <iostream>
#include <memory>
// 1. 产品接口
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& message) = 0;
};
// 2. 具体产品
class FileLogger : public Logger {
public:
void log(const std::string& message) override {
std::cout << "写入文件日志: " << message << std::endl;
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& message) override {
std::cout << "控制台日志: " << message << std::endl;
}
};
// 3. 工厂接口
class LoggerFactory {
public:
virtual ~LoggerFactory() = default;
virtual std::unique_ptr<Logger> createLogger() = 0;
};
// 4. 具体工厂
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>();
}
};
// 5. 客户端使用
int main() {
std::unique_ptr<LoggerFactory> factory =
std::make_unique<FileLoggerFactory>();
auto logger = factory->createLogger();
logger->log("系统启动成功");
return 0;
}
避坑指南:我曾经在 C++ 项目里直接用裸指针返回产品对象,结果调用方忘了 delete,内存泄漏查了好几天。后来统一改用 std::unique_ptr,问题彻底解决。工厂方法返回智能指针,是 C++ 的最佳实践。
参数化工厂方法
有时候,一个工厂方法需要根据参数创建不同的产品。这就是参数化工厂方法。
举个例子,日志系统需要根据日志级别决定输出方式:
// Java 参数化工厂方法
public interface LoggerFactory {
Logger createLogger(String type);
}
public class SimpleLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger(String type) {
switch (type) {
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
default:
throw new IllegalArgumentException("未知日志类型: " + type);
}
}
}
注意:参数化工厂方法虽然方便,但本质上又回到了简单工厂的模式。如果参数类型经常变化,建议还是用多个具体工厂。我一般只在参数种类固定且很少变动时,才用参数化方式。
工厂方法 vs 简单工厂
| 对比维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 开闭原则 | 违反(新增产品需改工厂类) | 符合(新增产品只需新增工厂子类) |
| 复杂度 | 低,一个工厂类搞定 | 中,需要多个工厂子类 |
| 适用场景 | 产品种类少且稳定 | 产品种类多或经常扩展 |
| 代码量 | 少 | 稍多,但更灵活 |
实际项目中的应用
我在做支付系统时,用过工厂方法模式。不同支付渠道(微信、支付宝、银联)各自有独立的对接逻辑。每个渠道一个具体工厂,客户端只需要知道渠道标识,就能拿到对应的支付处理器。
后来新增了一个「云闪付」渠道,我只需要新建一个 UnionPayFactory 和 UnionPayProcessor,完全不用动已有的代码。测试通过后直接上线,心里踏实得很。
我的建议:如果你发现代码里有很多 if-else 或 switch 在判断「创建哪个对象」,而且这些判断经常要改,那就该考虑用工厂方法模式了。它虽然多写几个类,但换来的是长期的可维护性。
总结
工厂方法模式的核心就一句话:让子类决定创建什么对象。
它把「创建对象」这个职责从客户端剥离出来,交给专门的工厂子类。这样客户端只依赖抽象的工厂接口和产品接口,具体实现完全隔离。
你想想看,如果你的项目里有一个对象创建逻辑经常变化,或者有多种实现需要灵活切换,工厂方法模式就是你的好帮手。它不复杂,但很实用——我这些年用下来,几乎没有哪个项目完全用不上它。
公众号:蓝海资料掘金营,微信deep3321