Menu
Coddy logo textTech

Factory и Abstract Factory

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 92 из 104.

Шаблон Factory делегирует создание объектов отдельному методу или class, позволяя вам create объекты без указания их точного class. Это полезно, когда логика создания сложна или когда вы хотите отделить клиентский код от concrete-реализаций.

Простая фабрика использует метод, который возвращает разные производные типы в зависимости от входных данных:

#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;
}

Abstract Factory развивает эту идею дальше, создавая семейства связанных объектов. Вместо одного фабричного метода у вас есть интерфейс factory с несколькими методами создания:

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

Задание

Легко

Давайте создадим систему Генератор документов с использованием шаблонов Factory и Abstract Factory. Вы создадите систему, способную генерировать различные типы документов (отчёты и счета) в разных выходных форматах (PDF и HTML), демонстрируя, как factory отделяют создание объектов от клиентского кода.

Вы организуете код в четырёх файлах:

  • Document.h: Определите иерархию продуктов документов.

    Создайте абстрактный базовый class Document с чистым виртуальным методом generate(), который возвращает строку с описанием сгенерированного документа, и виртуальным деструктором.

    Затем создайте четыре concrete class документов:

    • PDFReport: generate() возвращает "Generating PDF Report"
    • HTMLReport: generate() возвращает "Generating HTML Report"
    • PDFInvoice: generate() возвращает "Generating PDF Invoice"
    • HTMLInvoice: generate() возвращает "Generating HTML Invoice"
  • DocumentFactory.h: Реализуйте простую factory function и abstract factory.

    Сначала создайте простую factory function с именем createDocument, которая принимает две строки: тип документа ("report" или "invoice") и формат ("pdf" или "html"). Она должна возвращать std::unique_ptr<Document>, указывающий на подходящий concrete документ. Для недопустимых комбинаций возвращайте nullptr.

    Затем создайте abstract factory class с именем DocumentFactory с двумя чистыми виртуальными методами:

    • createReport(): возвращает std::unique_ptr<Document>
    • createInvoice(): возвращает std::unique_ptr<Document>

    Реализуйте две concrete factory:

    • PDFFactory: создаёт PDFReport и PDFInvoice
    • HTMLFactory: создаёт HTMLReport и HTMLInvoice
  • DocumentService.h: Создайте сервис, использующий abstract factory.

    Определите class DocumentService, который принимает std::unique_ptr<DocumentFactory> в своём конструкторе. В нём должен быть метод с именем generateAllDocuments(), использующий factory для создания отчёта и счета, а затем выводящий результат вызова generate() для каждого из них, по одному в строке.

  • main.cpp: Продемонстрируйте оба подхода с factory.

    Считайте два входных значения:

    1. Тип документа (report или invoice)
    2. Формат (pdf или html)

    Сначала используйте простую factory function для создания документа на основе входных данных. Выведите результат вызова generate() для него.

    Затем выведите пустую строку.

    Далее создайте DocumentService с подходящей factory на основе введённого формата (используйте PDFFactory для pdf, HTMLFactory для html). Вызовите generateAllDocuments(), чтобы показать, как abstract factory создаёт семейство связанных документов.

Например, при входных данных report и pdf:

Generating PDF Report

Generating PDF Report
Generating PDF Invoice

При входных данных invoice и html:

Generating HTML Invoice

Generating HTML Report
Generating HTML Invoice

Обратите внимание, как простая factory создаёт один документ на основе обоих параметров, тогда как abstract factory гарантирует, что все документы, созданные сервисом, используют один и тот же формат. Это ключевое преимущество Abstract Factory, когда вам нужны семейства связанных объектов, которые должны согласованно работать вместе.

Попробуйте сами

#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 (PDFFactory для "pdf", HTMLFactory для "html")
    // Вызовите generateAllDocuments() для демонстрации абстрактной фабрики
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++