You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++工厂方法模式抽象化实现的常见问题与最佳实践咨询

在C++中实现工厂方法模式:平衡抽象与细节隐藏的最佳实践

你的问题切中了工厂方法模式在实际开发中的核心痛点——既要保持基类的抽象性,又要避免暴露派生类的实现细节。下面我会结合你的代码,给出几种常用且成熟的解决方案:

方案一:将派生类完全隐藏在实现文件中(最简洁的隐藏方式)

这种思路下,派生类根本不需要对外提供头文件,所有定义都放在基类的.cpp文件(或者仅内部可见的私有头文件)里。这样外部代码完全感知不到派生类的存在,完美隐藏细节。

修改你的代码示例:

// graphic-api.hpp(对外仅暴露这个头文件)
#include <memory>
#include "window.hpp" // 假设Window的定义在这里

class GraphicApi {
public:
    enum class Type {
        VULKAN,
        OPENGL, // 可以随时扩展
        DIRECTX
    };
    virtual void render() = 0;
    virtual ~GraphicApi() = default;
    static std::unique_ptr<GraphicApi> make(const Window& window, Type type);
};
// graphic-api.cpp
#include "graphic-api.hpp"
#include <cassert>
#include <vulkan/vulkan.hpp> // 直接包含Vulkan头文件,外部看不到

// 派生类完全定义在cpp内部,对外不可见
class VulkanApi : public GraphicApi {
public:
    VulkanApi(const Window& window) {
        // 初始化Vulkan实例等逻辑
        instance = vk::UniqueInstance{vk::InstanceCreateInfo{}};
    }
    void render() override {
        // Vulkan渲染逻辑
    }
private:
    vk::UniqueInstance instance;
    // 所有Vulkan相关的私有成员都在这里,外部完全看不到
    // ...
};

// 工厂方法实现
std::unique_ptr<GraphicApi> GraphicApi::make(const Window& window, GraphicApi::Type type) {
    switch (type) {
        case GraphicApi::Type::VULKAN:
            return std::make_unique<VulkanApi>(window);
        // 后续添加OPENGL/DIRECTX时,直接在这里加对应的内部派生类即可
        default:
            assert(false && "Unsupported Graphic API");
            return nullptr;
    }
}

优点:

  • 派生类的实现细节100%隐藏,外部代码只依赖GraphicApi抽象
  • 不需要额外的头文件,结构简洁
  • 扩展新的API时,只需要修改.cpp文件,不影响对外接口

注意事项:

  • 派生类只能通过工厂方法创建,外部无法直接实例化(这也是工厂模式的初衷)
  • 如果派生类需要被多个.cpp文件使用(这种场景在工厂模式下很少见),可以把派生类定义放在一个内部私有头文件(比如graphic-api-private.hpp)里,只在需要的.cpp中包含,不对外发布。

方案二:用PImpl惯用法隐藏派生类的私有细节

如果因为某些原因必须给派生类提供头文件(比如需要让其他代码直接使用派生类的某些公共接口,虽然工厂模式下不推荐),PImpl是解决头文件泄露细节的标准方案。

修改你的vulkan-api.hpp:

// vulkan-api.hpp(对外提供,但仅暴露必要接口)
#include "graphic-api.hpp"
#include <memory>

class VulkanApi : public GraphicApi {
public:
    VulkanApi(const Window& window);
    void render() override;
    ~VulkanApi() override; // 必须声明,因为PImpl指针需要销毁

private:
    struct Impl; // 前向声明私有实现结构体
    std::unique_ptr<Impl> pImpl; // 指向实现细节的指针
};
// vulkan-api.cpp
#include "vulkan-api.hpp"
#include <vulkan/vulkan.hpp>

// 私有实现结构体,包含所有Vulkan相关细节
struct VulkanApi::Impl {
    vk::UniqueInstance instance;
    // 其他私有成员和方法
    // ...

    Impl(const Window& window) {
        instance = vk::UniqueInstance{vk::InstanceCreateInfo{}};
    }
};

VulkanApi::VulkanApi(const Window& window) : pImpl(std::make_unique<Impl>(window)) {}

void VulkanApi::render() {
    // 调用pImpl里的逻辑
    // ...
}

VulkanApi::~VulkanApi() = default; // 在这里生成析构函数,因为Impl的定义可见了

然后在graphic-api.cpp中只需包含vulkan-api.hpp即可:

// graphic-api.cpp
#include "graphic-api.hpp"
#include "vulkan-api.hpp"
#include <cassert>

std::unique_ptr<GraphicApi> GraphicApi::make(const Window& window, GraphicApi::Type type) {
    switch (type) {
        case GraphicApi::Type::VULKAN:
            return std::make_unique<VulkanApi>(window);
        default:
            assert(false && "Unsupported Graphic API");
            return nullptr;
    }
}

优点:

  • 派生类的头文件非常干净,只暴露公共接口,私有细节完全隐藏在.cpp里
  • 派生类可以被外部代码直接使用(如果有需要),同时不泄露实现细节

缺点:

  • 增加了一层间接性,稍微增加了一点运行时开销(可以忽略不计)
  • 需要额外管理PImpl指针,代码量略有增加

哪种方案更合适?

  • 如果你的场景完全符合工厂模式的初衷:外部只通过基类接口与对象交互,那么方案一是最佳选择,它最简洁且完全隐藏细节。
  • 如果必须让外部代码直接访问派生类的某些公共接口(这种情况比较少见,因为工厂模式就是为了封装对象创建),那么方案二是标准解决方案。

总的来说,在大多数工厂方法模式的应用场景中,方案一是最常用也最推荐的实现方式。

内容的提问来源于stack exchange,提问作者Cyril G.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 00:22:52