STL与工厂模式:简单工厂、工厂方法、抽象工厂

工厂模式这东西,说白了就是帮你「创建对象」的套路。我早年写C++项目时,经常在代码里到处new对象,后来维护起来简直想抽自己。直到我把工厂模式和STL结合起来用,才真正体会到什么叫「优雅」。

今天咱们就聊聊三种工厂模式——简单工厂、工厂方法、抽象工厂。我会结合STL容器和智能指针,给你展示实际项目中怎么用。

为什么工厂模式在C++里特别重要?

你想想看,C++里对象的生命周期管理本身就是个头疼事。再加上多态、继承这些特性,如果创建对象的逻辑散落在各处,代码会变得又臭又硬。

工厂模式的核心价值就两点:

  • 解耦:调用方不需要知道具体类名,只需要知道接口
  • 集中管理:所有创建逻辑放在一个地方,改起来方便

我在项目中遇到过最典型的场景:一个日志系统需要支持文件日志、控制台日志、网络日志。如果每次新增日志类型都要改调用方的代码,那简直是噩梦。工厂模式就是来解决这个问题的。

简单工厂模式:最直接的方案

简单工厂,说白了就是一个函数,根据参数返回不同类型的对象。它不算真正的设计模式,但胜在简单实用。

#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <functional>

// 产品基类
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 {
        std::cout << "[File] " << msg << std::endl;
    }
};

class ConsoleLogger : public Logger {
public:
    void log(const std::string& msg) override {
        std::cout << "[Console] " << msg << std::endl;
    }
};

// 简单工厂
class LoggerFactory {
public:
    static std::unique_ptr<Logger> create(const std::string& type) {
        if (type == "file") return std::make_unique<FileLogger>();
        if (type == "console") return std::make_unique<ConsoleLogger>();
        return nullptr;
    }
};

int main() {
    auto logger = LoggerFactory::create("file");
    logger->log("Hello, Factory!");
    return 0;
}
我的习惯:简单工厂适合产品种类少、变化不频繁的场景。如果产品类型超过5种,我会考虑用注册表模式,避免if-else泛滥。

工厂方法模式:把创建延迟到子类

简单工厂有个硬伤——每次新增产品都要改工厂函数,违反了开闭原则。工厂方法模式把创建逻辑放到子类里,每个子类负责创建一种产品。

// 工厂基类
class LoggerCreator {
public:
    virtual ~LoggerCreator() = default;
    virtual std::unique_ptr<Logger> createLogger() = 0;
    
    // 模板方法:工厂方法通常配合模板方法使用
    void doLog(const std::string& msg) {
        auto logger = createLogger();
        logger->log(msg);
    }
};

class FileLoggerCreator : public LoggerCreator {
public:
    std::unique_ptr<Logger> createLogger() override {
        return std::make_unique<FileLogger>();
    }
};

class ConsoleLoggerCreator : public LoggerCreator {
public:
    std::unique_ptr<Logger> createLogger() override {
        return std::make_unique<ConsoleLogger>();
    }
};

int main() {
    std::unique_ptr<LoggerCreator> creator = std::make_unique<FileLoggerCreator>();
    creator->doLog("Factory Method in action!");
    return 0;
}

关键区别:简单工厂用参数决定创建什么,工厂方法用多态决定。后者更灵活,但类数量会翻倍。

抽象工厂模式:创建产品族

抽象工厂解决的是「产品族」的问题。比如一套UI组件,有按钮、文本框、下拉框,它们需要风格统一——Windows风格或Mac风格。抽象工厂就是用来创建这样一组相关产品的。

// 抽象产品
class Button {
public:
    virtual ~Button() = default;
    virtual void render() = 0;
};

class TextBox {
public:
    virtual ~TextBox() = default;
    virtual void render() = 0;
};

// 具体产品:Windows风格
class WinButton : public Button {
public:
    void render() override { std::cout << "Windows Button\n"; }
};

class WinTextBox : public TextBox {
public:
    void render() override { std::cout << "Windows TextBox\n"; }
};

// 具体产品:Mac风格
class MacButton : public Button {
public:
    void render() override { std::cout << "Mac Button\n"; }
};

class MacTextBox : public TextBox {
public:
    void render() override { std::cout << "Mac TextBox\n"; }
};

// 抽象工厂
class UIFactory {
public:
    virtual ~UIFactory() = default;
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<TextBox> createTextBox() = 0;
};

class WinFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WinButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<WinTextBox>();
    }
};

class MacFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<MacTextBox>();
    }
};
我曾经踩过的坑:抽象工厂很容易过度设计。如果你的产品族只有一两个产品,或者产品之间没有强关联,用抽象工厂反而增加复杂度。记住:模式是工具,不是目的。

STL如何助力工厂模式?

STL容器和智能指针是工厂模式的绝配。我总结了几种常见组合:

STL组件 工厂模式中的应用 我的使用场景
std::unique_ptr 工厂方法返回独占所有权 绝大多数工厂场景
std::shared_ptr 需要共享对象时 缓存池、对象池
std::unordered_map 注册表模式,存储创建函数 插件系统、动态加载
std::function 存储可调用对象作为创建器 策略模式+工厂

举个例子,用std::unordered_mapstd::function实现一个注册表工厂:

class RegistryFactory {
private:
    using Creator = std::function<std::unique_ptr<Logger>()>;
    std::unordered_map<std::string, Creator> registry_;
    
public:
    void registerType(const std::string& name, Creator creator) {
        registry_[name] = std::move(creator);
    }
    
    std::unique_ptr<Logger> create(const std::string& name) {
        auto it = registry_.find(name);
        if (it != registry_.end()) {
            return it->second();
        }
        return nullptr;
    }
};

// 使用
RegistryFactory factory;
factory.registerType("file", []{ return std::make_unique<FileLogger>(); });
factory.registerType("console", []{ return std::make_unique<ConsoleLogger>(); });

auto logger = factory.create("file");
logger->log("Registry pattern!");
我的建议:注册表工厂是我最常用的模式。它不需要修改工厂类就能新增产品,而且可以动态注册。配合std::shared_ptr还能实现对象缓存,性能杠杠的。

三种模式对比

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

特性 简单工厂 工厂方法 抽象工厂
复杂度
扩展性 差(改工厂函数) 好(新增子类) 好(新增工厂子类)
产品数量 单个产品 单个产品 产品族
适用场景 产品少、变化少 产品多、需要扩展 产品族、风格统一

核心知识体系

下面这张图帮你理清三种工厂模式的关系和适用边界:

工厂模式体系 简单工厂 工厂方法 抽象工厂 核心:一个静态函数 参数决定产品类型 优点:简单直接 缺点:违反开闭原则 适用:产品少且稳定 STL搭配:unique_ptr 核心:虚函数创建 子类决定具体产品 优点:扩展性好 缺点:类数量翻倍 适用:产品种类多 STL搭配:shared_ptr 核心:创建产品族 多个相关产品 优点:风格统一 缺点:扩展产品族困难 适用:UI组件、主题 STL搭配:function+map 选择原则:从简单开始,按需演进

实战建议

说了这么多,我最后给你几条实在的建议:

  • 别一上来就抽象工厂:我见过太多人为了用模式而用模式。先写简单工厂,等发现if-else泛滥了再重构。
  • 智能指针是标配:工厂方法返回裸指针的时代已经过去了。用std::unique_ptrstd::shared_ptr,让所有权清晰。
  • 注册表模式很香:如果你需要动态扩展产品类型,用std::unordered_map+std::function实现注册表工厂,比继承体系灵活得多。
  • 小心循环依赖:工厂模式容易导致头文件互相包含。我习惯把工厂声明放在单独的头文件里,或者用前向声明。

一句话总结:工厂模式的核心是「把创建对象这件事封装起来」。STL提供了智能指针和容器,让这个封装更安全、更灵活。从简单工厂开始,按需演进到工厂方法或抽象工厂,别过度设计。

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