C++工厂模式的实现变体详解与应用场景对比分析插图

C++工厂模式的实现变体详解与应用场景对比分析:从简单工厂到抽象工厂的实战演进

大家好,作为一名在C++领域摸爬滚打多年的开发者,我发现设计模式是代码从“能跑”到“优雅”的关键分水岭。其中,工厂模式家族绝对是使用最频繁、也最容易产生混淆的模式之一。今天,我就结合自己的实战经验和踩过的坑,来详细拆解C++中工厂模式的几种核心变体,并对比它们各自的应用场景。你会发现,没有“最好”的模式,只有“最合适”的场景。

一、缘起:为什么我们需要工厂?——一个简单的场景

假设我们在开发一个图形编辑器,需要创建圆形、矩形等不同形状对象。最直接的写法可能是这样:

// 糟糕的写法:紧耦合
if (type == "Circle") {
    Circle* shape = new Circle();
    // 操作shape...
} else if (type == "Rectangle") {
    Rectangle* shape = new Rectangle();
    // 操作shape...
}

这段代码的问题显而易见:创建逻辑散落在业务代码中,每次新增形状都要修改多处代码,违反了“开闭原则”。这时,工厂模式就该登场了——它的核心思想就是将对象的创建与使用分离

二、变体一:简单工厂模式(Simple Factory)——快速上手

简单工厂不算GoF 23种设计模式之一,但它是最直观的入门选择。它提供一个静态方法,根据传入的参数决定创建哪种产品。

// 产品基类与具体产品
class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing a Circle.n"; }
};

class Rectangle : public Shape {
public:
    void draw() override { std::cout <draw();
        delete shape; // 痛点2:工厂通常不负责生命周期,容易内存泄漏
    }
    return 0;
}

实战感受与踩坑提示:简单工厂适合产品类型固定、变化不频繁的场景。我曾在工具类的小项目中用它,快速清晰。但它的硬伤很明显:违反开闭原则。每次加新产品都得改工厂类,这在大型项目或库设计中是致命的。另外,记得管理好返回的裸指针的生命周期,这里用智能指针会更安全。

三、变体二:工厂方法模式(Factory Method)——多态化创建

工厂方法模式解决了简单工厂的“开闭”问题。它为每个产品提供一个独立的工厂类,这样新增产品时,只需增加新的工厂类,无需修改已有代码。

// 工厂抽象基类
class ShapeFactory {
public:
    virtual Shape* createShape() = 0;
    virtual ~ShapeFactory() = default;
};

// 具体工厂类,每个产品对应一个
class CircleFactory : public ShapeFactory {
public:
    Shape* createShape() override {
        return new Circle(); // 可在此处进行复杂的初始化
    }
};

class RectangleFactory : public ShapeFactory {
public:
    Shape* createShape() override {
        return new Rectangle();
    }
};

// 使用:客户端代码依赖于工厂抽象
int main() {
    ShapeFactory* factory = new CircleFactory(); // 可通过配置决定用哪个工厂
    Shape* shape = factory->createShape();
    shape->draw();

    delete shape;
    delete factory;
    return 0;
}

应用场景对比:工厂方法模式在框架设计中非常有用。比如,你的库定义了一个`Document`基类和`DocumentFactory`,用户想新增一个`MyDocument`,他只需要继承这两个类并实现即可,完全不用动你的库代码。这就是“扩展开放,修改关闭”。它的缺点是会导致类爆炸——产品越多,工厂类也越多。

四、变体三:抽象工厂模式(Abstract Factory)——创建产品族

这是工厂模式家族中最“重量级”的一位。当你的系统需要创建多个相互关联或依赖的产品对象(一个产品族)时,就该它出场了。经典例子是跨平台的UI组件:每一套平台(如Windows、Mac)都是一个产品族,包含按钮、文本框等系列产品。

// 抽象产品:按钮和文本框
class Button {
public:
    virtual void render() = 0;
    virtual ~Button() = default;
};
class TextBox {
public:
    virtual void show() = 0;
    virtual ~TextBox() = default;
};

// 具体产品:Windows系列
class WinButton : public Button {
public:
    void render() override { std::cout << "Windows Style Buttonn"; }
};
class WinTextBox : public TextBox {
public:
    void show() override { std::cout << "Windows Style TextBoxn"; }
};

// 具体产品:Mac系列
class MacButton : public Button {
public:
    void render() override { std::cout << "macOS Style Buttonn"; }
};
class MacTextBox : public TextBox {
public:
    void show() override { std::cout <createButton(); // 创建的是同一风格的产品
        textBox = factory->createTextBox();
    }
    void paint() {
        if(button) button->render();
        if(textBox) textBox->show();
    }
};

核心优势与选择时机:抽象工厂保证了从同一个工厂出来的产品(如`WinButton`和`WinTextBox`)是兼容的、属于同一风格的。它特别适合系统需要切换整个产品族的场景。它的复杂度最高,如果产品族中需要新增一个产品(比如再加一个`CheckBox`),就需要修改所有工厂类,这是它的局限。

五、实战选择与演进思考

回顾一下:

  • 简单工厂:逻辑简单,但扩展要改代码。适合小型工具、配置明确且稳定的模块。
  • 工厂方法:遵循开闭原则,每个产品对应一个工厂。适合框架设计、插件系统,让用户扩展。
  • 抽象工厂:处理产品族,保证系列产品兼容性。适合跨平台UI、数据库访问层(不同DB的连接、命令等系列对象)等。

在我的一个跨平台渲染引擎项目中,就经历了从简单工厂到抽象工厂的演进。初期只有OpenGL渲染器,用简单工厂创建`Shader`、`Texture`。后来需要支持Vulkan,立刻发现产品(OpenGL Shader和Vulkan Shader)是互不兼容的两大族。于是重构为抽象工厂,定义`RenderFactory`接口,派生出`GLFactory`和`VKFactory`,轻松管理了两套渲染对象,且客户端代码几乎不变。

最后给一个终极建议:不要为了用模式而用模式。当你发现代码中充斥着`new`关键字和复杂的条件判断来创建对象时,就是考虑工厂模式的信号。先从最简单的需求出发,随着系统复杂度的增长,再逐步演进到更解耦的模式变体。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。