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, когда вам нужно гарантировать, что связанные объекты работают вместе согласованно.
Задание
ЛегкоДавайте создадим систему Document Generator (Генератор документов), используя паттерны Factory (Фабрика) и Abstract Factory (Абстрактная фабрика). Вы создадите систему, которая может генерировать различные типы документов (отчеты и счета) для разных форматов вывода (PDF и HTML), демонстрируя, как фабрики отделяют создание объектов от клиентского кода.
Вы организуете свой код в четырех файлах:
Document.h: Определите иерархию продуктов документов.Создайте абстрактный базовый класс
Documentс чисто виртуальным методомgenerate(), который возвращает строку, описывающую сгенерированный документ, и виртуальным деструктором.Затем создайте четыре конкретных класса документов:
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иPDFInvoiceHTMLFactory— создаетHTMLReportиHTMLInvoice
DocumentService.h: Создайте сервис, использующий абстрактную фабрику.Определите класс
DocumentService, который принимаетstd::unique_ptr<DocumentFactory>в своем конструкторе. У него должен быть методgenerateAllDocuments(), который использует фабрику для создания отчета и счета, а затем выводит результат вызоваgenerate()для каждого из них, по одному в строке.main.cpp: Продемонстрируйте оба подхода к фабрикам.Считайте два входных значения:
- Тип документа (
reportилиinvoice) - Формат (
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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер