Menu
Coddy logo textTech

Factory и Abstract Factory

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 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;
}

Абстрактная фабрика (Abstract 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

Задание

Легко

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

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

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

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

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

    • PDFReportgenerate() возвращает "Generating PDF Report"
    • HTMLReportgenerate() возвращает "Generating HTML Report"
    • PDFInvoicegenerate() возвращает "Generating PDF Invoice"
    • HTMLInvoicegenerate() возвращает "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 с соответствующей фабрикой на основе введенного формата (используйте PDFFactory для pdf, HTMLFactory для html). Вызовите generateAllDocuments(), чтобы показать, как абстрактная фабрика создает семейство связанных документов.

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

Generating PDF Report

Generating PDF Report
Generating PDF Invoice

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

Generating HTML Invoice

Generating HTML Report
Generating HTML Invoice

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

Шпаргалка

Паттерн Фабрика (Factory pattern) делегирует создание объектов отдельному методу или классу, позволяя создавать объекты без указания их конкретного класса. Это отделяет клиентский код от конкретных реализаций.

Простая фабрика (simple factory) использует функцию, которая возвращает различные производные типы в зависимости от входных данных:

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

// Factory function
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 pattern) создает семейства связанных объектов. Вместо одного фабричного метода вы используете интерфейс фабрики с несколькими методами создания:

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

// Abstract Factory
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>();
    }
};

Используйте Фабрику, когда вам нужно гибкое создание объектов. Используйте Абстрактную фабрику, когда вам нужно гарантировать, что связанные объекты согласованно работают вместе как семейство.

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

#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Проверьте себя

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

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