工厂方法模式:简单工厂 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写了三十多个分支,维护起来简直想哭。

避坑指南:我曾经接手过一个项目,简单工厂里塞了四十多种产品。每次加新产品,都要改工厂代码,还经常改出bug。后来我重构成了工厂方法模式,才算是解脱了。

工厂方法模式:把创建权交给子类

工厂方法模式的核心思想是:定义一个创建对象的接口,让子类决定实例化哪个类。说白了,就是把「创建」这个动作延迟到子类。

还是日志记录器的例子,用工厂方法模式改写:

// 抽象工厂
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

这就是一个产品等级结构。工厂方法模式中,每个具体工厂对应一个具体产品。你想想看,如果产品等级结构很深(比如有抽象层、中间层、具体层),工厂方法也能很好地应对。

我画了一张图,帮你理清关系:

工厂方法模式结构图 LoggerFactory + createLogger() FileLoggerFactory + createLogger() ConsoleLoggerFactory + createLogger() NetworkLoggerFactory + createLogger() Logger + log(string) 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;
}
个人经验:我建议在工厂方法中使用 std::unique_ptr 而非原始指针。这样既保证了异常安全,又明确了所有权语义。如果你需要共享工厂实例,可以考虑 std::shared_ptr。

简单工厂 vs 工厂方法:怎么选?

我整理了一个对比表格,方便你快速决策:

对比维度 简单工厂 工厂方法
核心思想 一个工厂函数,根据参数创建不同产品 每个产品对应一个工厂子类
开闭原则 违反。新增产品需要修改工厂函数 符合。新增产品只需新增工厂子类
代码复杂度 低。一个函数搞定 中等。需要定义工厂接口和多个子类
适用场景 产品种类少且稳定,不常变化 产品种类多,或经常扩展
维护成本 产品增多时,工厂函数变得臃肿 产品增多时,新增类即可,不影响已有代码

怎么选?我的建议是:如果你确定产品类型不会超过三五个,而且未来不会频繁变动,简单工厂完全够用。但如果你在做一个框架或库,不确定用户会扩展出什么产品,那就用工厂方法——给用户留好扩展点。

一句话总结:简单工厂是「一个工厂管所有」,工厂方法是「一个产品一个工厂」。前者图省事,后者图扩展。

最后说一句,工厂方法模式在C++中还有一个变体——模板工厂方法。利用模板参数来指定产品类型,可以减少子类的数量。不过那是另一个话题了,咱们下次再聊。


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