工厂方法模式:让对象的创建更灵活

工厂方法模式,说白了就是「把创建对象这件事,交给子类去决定」。

我刚开始接触设计模式时,觉得简单工厂就够用了。直到有一次,我在做一个跨平台的消息推送系统——Android、iOS、Web 各一套实现。简单工厂里塞满了 if-else,每次加一个新平台,就要改工厂类。改着改着,我意识到:这违反了开闭原则。嗯,工厂方法模式就是用来解决这个问题的。

模式的核心思想

工厂方法模式定义了一个创建对象的接口,但让子类决定实例化哪个类。它把对象的实例化延迟到子类。

你想想看,这样做的好处是什么?

  • 解耦:客户端不需要知道具体产品类名
  • 扩展性:新增产品时,不需要修改现有工厂接口
  • 符合开闭原则:对扩展开放,对修改关闭

核心角色:

  • Product(产品接口):定义产品的公共接口
  • ConcreteProduct(具体产品):实现产品接口的具体类
  • Creator(工厂接口):声明工厂方法,返回产品对象
  • ConcreteCreator(具体工厂):实现工厂方法,创建具体产品

模式结构图

先看一张图,把整体脉络理清楚:

工厂方法模式结构图 <<interface>> Creator + factoryMethod(): Product ConcreteCreatorA + factoryMethod(): Product → return new ConcreteProductA() ConcreteCreatorB + factoryMethod(): Product → return new ConcreteProductB() <<interface>> Product + use(): void ConcreteProductA + use(): void ConcreteProductB + use(): void 继承(泛化) 依赖(创建)

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 简单工厂

对比维度 简单工厂 工厂方法
开闭原则 违反(新增产品需改工厂类) 符合(新增产品只需新增工厂子类)
复杂度 低,一个工厂类搞定 中,需要多个工厂子类
适用场景 产品种类少且稳定 产品种类多或经常扩展
代码量 稍多,但更灵活

实际项目中的应用

我在做支付系统时,用过工厂方法模式。不同支付渠道(微信、支付宝、银联)各自有独立的对接逻辑。每个渠道一个具体工厂,客户端只需要知道渠道标识,就能拿到对应的支付处理器。

后来新增了一个「云闪付」渠道,我只需要新建一个 UnionPayFactoryUnionPayProcessor,完全不用动已有的代码。测试通过后直接上线,心里踏实得很。

我的建议:如果你发现代码里有很多 if-else 或 switch 在判断「创建哪个对象」,而且这些判断经常要改,那就该考虑用工厂方法模式了。它虽然多写几个类,但换来的是长期的可维护性。

总结

工厂方法模式的核心就一句话:让子类决定创建什么对象

它把「创建对象」这个职责从客户端剥离出来,交给专门的工厂子类。这样客户端只依赖抽象的工厂接口和产品接口,具体实现完全隔离。

你想想看,如果你的项目里有一个对象创建逻辑经常变化,或者有多种实现需要灵活切换,工厂方法模式就是你的好帮手。它不复杂,但很实用——我这些年用下来,几乎没有哪个项目完全用不上它。


公众号:蓝海资料掘金营,微信deep3321