Паттерн Компоновщик
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 101 из 104.
Паттерн Composite позволяет одинаково работать с отдельными объектами и группами объектов. Он объединяет объекты в древовидные структуры, где отдельные элементы и контейнеры элементов используют один и тот же интерфейс. Это идеально подходит для представления иерархий, таких как файловые системы, организационные диаграммы или компоненты пользовательского интерфейса.
Шаблон состоит из трёх ключевых частей: интерфейса Component, определяющего общие операции, классов Leaf, представляющих отдельные объекты, и классов Composite, содержащих children и делегирующих им операции:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Интерфейс компонента
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Leaf - представляет отдельные файлы
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Composite - содержит другие компоненты
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};Составной объект Folder хранит children и реализует операции, перебирая их. Когда вы вызываете getSize() для папки, он рекурсивно вычисляет общий размер всех содержащихся элементов. Клиентскому коду не нужно знать, работает ли он с файлом или папкой — оба отвечают на один и тот же интерфейс.
Используйте Composite, когда вам нужно представить иерархии «часть — целое» и вы хотите, чтобы клиенты единообразно обрабатывали отдельные объекты и композиции.
Задание
ЛегкоДавайте создадим систему Organization Chart с использованием паттерна Composite. Вы создадите иерархию, в которой как отдельные employees, так и departments (содержащие других employees или sub-departments) могут обрабатываться единообразно. Это отражает структуру реальных компаний: departments содержат людей и другие departments, образуя древовидную структуру.
Вы распределите код по трём файлам:
OrgComponent.h: определите интерфейс компонента, который будут реализовывать и employees, и departments.Создайте абстрактный класс
OrgComponentс методами:getName(): возвращает имя компонентаgetSalary(): возвращает общую зарплату (для employees — их собственную зарплату; для departments — сумму всех содержащихся зарплат)display(int indent = 0): отображает компонент с правильным отступом
Добавьте виртуальный деструктор.
Organization.h: реализуйте leaf- и composite-классы.Создайте класс
Employee(leaf), который хранит имя и зарплату. Его методdisplay()должен выводить информацию об employee в следующем формате:[indent spaces]- [name] ($[salary])Создайте класс
Department(composite), который хранит имя и коллекцию дочерних элементовOrgComponentс использованиемstd::shared_ptr. Реализуйте:add(std::shared_ptr<OrgComponent> component): добавляет дочерний элемент в departmentgetSalary(): рекурсивно вычисляет общую зарплату всех участниковdisplay(): выводит имя department в скобках, а затем отображает всех детей с увеличенным отступом (добавляя 2 пробела на каждый уровень)
Формат отображения department должен быть таким:
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: создайте и отобразите структуру организации.Прочитайте четыре входных значения:
- Имя company (строка)
- Имя department (строка)
- Имя и зарплата первого employee (формат:
name,salary) - Имя и зарплата второго employee (формат:
name,salary)
Создайте следующую структуру: создайте company (department верхнего уровня), добавьте в неё sub-department и добавьте обоих employees в этот sub-department. Затем отобразите всю organization и выведите общую зарплату company.
После отображения структуры выведите:
Total Salary: $[amount]
Например, для входных данных TechCorp, Engineering, Alice,75000 и Bob,65000:
[TechCorp]
[Engineering]
- Alice ($75000)
- Bob ($65000)
Total Salary: $140000Для входных данных StartupInc, Development, Carol,80000 и Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Обратите внимание, как работает getSalary(): он единообразно обрабатывает вызов как для employee, так и для department — department автоматически суммирует зарплаты всех своих участников. Клиентскому коду не нужно различать отдельных employees и целые departments при вычислении итогов или отображении иерархии.
Попробуйте сами
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Прочитать входные данные
std::string companyName;
std::string departmentName;
std::string employee1Input;
std::string employee2Input;
std::getline(std::cin, companyName);
std::getline(std::cin, departmentName);
std::getline(std::cin, employee1Input);
std::getline(std::cin, employee2Input);
// Вспомогательная лямбда для разбора формата "name,salary"
auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
size_t commaPos = input.find(',');
std::string name = input.substr(0, commaPos);
int salary = std::stoi(input.substr(commaPos + 1));
return {name, salary};
};
auto [name1, salary1] = parseEmployee(employee1Input);
auto [name2, salary2] = parseEmployee(employee2Input);
// TODO: Создать компанию как Department верхнего уровня
// TODO: Создать суб-департамент
// TODO: Создать два объекта Employee, используя разобранные данные
// TODO: Добавить сотрудников в суб-департамент
// TODO: Добавить суб-департамент в компанию
// TODO: Отобразить всю структуру организации
// TODO: Вывести общую зарплату в формате: Total Salary: $[amount]
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++