Menu
CoddyTech

工厂与抽象工厂

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 92 课,共 104 课。

工厂模式将对象的创建交给单独的方法或类,这样你无需指定对象的确切类即可创建对象。当创建逻辑复杂,或你想让客户端代码与具体实现解耦时,这种模式就很有用。

简单工厂使用一个方法,根据输入返回不同的派生类型:

#include <iostream>
#include <memory>

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

class WindowsButton : public Button {
public:
    void render() override { std::cout << "Windows button\n"; }
};

class MacButton : public Button {
public:
    void render() override { std::cout << "Mac button\n"; }
};

// 工厂函数
std::unique_ptr<Button> createButton(const std::string& os) {
    if (os == "windows") return std::make_unique<WindowsButton>();
    if (os == "mac") return std::make_unique<MacButton>();
    return nullptr;
}

抽象工厂通过创建相关对象的系列,进一步扩展了这种做法。你不再只有一个工厂方法,而是拥有一个包含多个创建方法的工厂接口:

class Checkbox {
public:
    virtual void check() = 0;
    virtual ~Checkbox() = default;
};

class WindowsCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Windows checkbox\n"; }
};

class MacCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Mac checkbox\n"; }
};

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

class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    std::unique_ptr<Checkbox> createCheckbox() override {
        return std::make_unique<WindowsCheckbox>();
    }
};

需要灵活地创建对象时,使用 Factory;需要确保相关对象始终协同工作时,使用 Abstract Factory。

challenge icon

挑战

简单

让我们使用工厂模式和抽象工厂模式构建一个文档生成器系统。你将创建一个系统,为不同的输出格式(PDF 和 HTML)生成不同类型的文档(报告和发票),并展示工厂如何将对象创建与客户端代码解耦。

你将把代码组织在 4 个文件中:

  • Document.h:定义文档产品层次结构。

    创建一个抽象基类 Document,其中包含一个纯虚方法 generate(),用于返回描述所生成文档的字符串,并包含一个虚析构函数。

    然后创建 4 个具体的文档类:

    • PDFReport:generate() 返回 "Generating PDF Report"
    • HTMLReport:generate() 返回 "Generating HTML Report"
    • PDFInvoice:generate() 返回 "Generating PDF Invoice"
    • HTMLInvoice:generate() 返回 "Generating HTML Invoice"
  • DocumentFactory.h:实现一个简单工厂函数和一个抽象工厂。

    首先,创建一个名为 createDocument 的简单工厂函数,它接收两个字符串:文档类型("report" 或 "invoice")和格式("pdf" 或 "html")。它应返回一个指向相应具体文档的 std::unique_ptr<Document>。如果组合无效,则返回 nullptr。

    然后,创建一个名为 DocumentFactory 的抽象工厂类,其中包含两个纯虚方法:

    • createReport():返回一个 std::unique_ptr<Document>
    • createInvoice():返回一个 std::unique_ptr<Document>

    实现两个具体工厂:

    • PDFFactory:创建 PDFReport 和 PDFInvoice
    • HTMLFactory:创建 HTMLReport 和 HTMLInvoice
  • DocumentService.h:创建一个使用抽象工厂的服务。

    定义一个 DocumentService 类,在其构造函数中接收一个 std::unique_ptr<DocumentFactory>。它应包含一个名为 generateAllDocuments() 的方法,该方法使用工厂创建一个报告和一张发票,然后分别调用它们的 generate() 并打印结果,每行一个。

  • main.cpp:演示这两种工厂方式。

    读取两个输入:

    1. 文档类型(report 或 invoice)
    2. 格式(pdf 或 html)

    首先,根据输入使用简单工厂函数创建一个文档。打印调用其 generate() 的结果。

    然后打印一个空行。

    接下来,根据格式输入创建一个使用相应工厂的 DocumentService(输入为 pdf 时使用 PDFFactory,输入为 html 时使用 HTMLFactory)。调用 generateAllDocuments(),展示抽象工厂如何创建一组相关文档。

例如,输入 report 和 pdf 时:

Generating PDF Report

Generating PDF Report
Generating PDF Invoice

输入 invoice 和 html 时:

Generating HTML Invoice

Generating HTML Report
Generating HTML Invoice

注意,简单工厂会根据两个参数创建单个文档,而抽象工厂会确保服务创建的所有文档都采用相同的格式。这就是抽象工厂的主要优势:当你需要一组能够协调一致地协同工作的相关对象时,它能满足你的需求。

亲手试试

#include <iostream>
#include <string>
#include <memory>
#include "Document.h"
#include "DocumentFactory.h"
#include "DocumentService.h"

using namespace std;

int main() {
    // 读取输入
    string documentType;
    string format;
    cin >> documentType;
    cin >> format;
    
    // TODO: 使用简单工厂函数创建文档
    // 根据 documentType 和 format 输入
    // 输出对该文档调用 generate() 的结果
    
    // 输出一个空行
    cout << endl;
    
    // TODO: 使用合适的工厂创建 DocumentService
    // 根据 format 输入选择("pdf" 对应 PDFFactory,"html" 对应 HTMLFactory)
    // 调用 generateAllDocuments(),演示抽象工厂
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器