Паттерн Компоновщик
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 61 из 70.
Паттерн Компоновщик (Composite) — это структурный паттерн, который позволяет группировать объекты в древовидные структуры и работать с одиночными объектами и их группами единообразно. Он идеально подходит для представления иерархий «часть-целое», когда вы хотите взаимодействовать как с отдельными элементами, так и с коллекциями через один и тот же интерфейс.
Паттерн состоит из трех ключевых компонентов: интерфейса компонента, определяющего общие операции, классов листьев (leaf), представляющих отдельные объекты, и составных классов (composite), которые содержат дочерние элементы и реализуют тот же интерфейс:
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}Прелесть этого паттерна заключается в том, что клиентам не нужно знать, работают ли они с отдельным файлом или с целой иерархией папок:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650Паттерн Компоновщик (Composite) идеально подходит для файловых систем, деревьев компонентов пользовательского интерфейса, организационных диаграмм или любых структур, где контейнеры и содержимое должны обрабатываться одинаково. Он упрощает клиентский код, устраняя необходимость различать простые и сложные элементы.
Задание
ЛегкоДавайте создадим систему организационной структуры, используя паттерн Компоновщик (Composite). Вы создадите структуру, в которой как отдельные сотрудники, так и отделы (содержащие сотрудников или другие отделы) могут рассматриваться единообразно, что позволит вам рассчитать общую стоимость заработной платы на любом уровне иерархии.
Вы организуете свой код в трех файлах:
OrgComponent.cs: Определите интерфейсIOrgComponentв пространстве именOrganizationс двумя членами: свойствомName(строка) и методомGetSalary(), который возвращает целое число. Этот интерфейс представляет собой общий контракт, которому будут следовать как отдельные сотрудники, так и отделы.OrgClasses.cs: Создайте два класса в том же пространстве имен:Employee— класс «листа», представляющий отдельного работника. Он принимает имя и зарплату в своем конструкторе и реализует интерфейс, возвращая собственную зарплату.Department— составной класс, представляющий группу. Он принимает имя в своем конструкторе и хранит список элементовIOrgComponent. Включите методAdd(IOrgComponent component)для добавления участников. Его методGetSalary()должен возвращать сумму зарплат всех его участников.
Program.cs: Постройте организационную структуру на основе входных данных и рассчитайте общую зарплату. Вы будете создавать отделы и сотрудников, вкладывать их соответствующим образом и демонстрировать, как паттерн Компоновщик позволяет единообразно обрабатывать всю иерархию.
Вы получите следующие входные данные:
- Количество создаваемых компонентов
- Для каждого компонента: тип (
employeeилиdepartment), за которым следует имя, а для сотрудников — их зарплата. Для отделов вы также получите имя родительского отдела (илиroot, если это отдел верхнего уровня)
Формат ввода для каждого компонента:
employee
{name}
{salary}
{parent_department}
department
{name}
{parent_department}После построения структуры выведите общую зарплату корневого отдела в формате {root_name} Total Salary: {amount}.
Например, если входные данные следующие:
5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QAВывод должен быть таким:
Engineering Total Salary: 13500Отдел Engineering содержит Alice (5000), Bob (4500) и отдел QA. QA содержит Charlie (4000). Когда вы вызываете GetSalary() для Engineering, он рекурсивно вычисляет общую сумму всех вложенных компонентов — демонстрируя, как паттерн Компоновщик позволяет работать со сложными древовидными структурами через простой, единообразный интерфейс!
Шпаргалка
Паттерн Компоновщик (Composite) — это структурный паттерн, который объединяет объекты в древовидные структуры и позволяет работать с отдельными объектами и их группами единообразно через общий интерфейс. Он идеально подходит для иерархий «часть-целое».
Паттерн состоит из трех ключевых компонентов:
- Интерфейс компонента (Component interface): определяет общие операции
- Классы листьев (Leaf classes): представляют отдельные объекты
- Классы компоновщиков (Composite classes): содержат дочерние элементы и реализуют тот же интерфейс
Пример базовой реализации:
// Интерфейс компонента
public interface IFileSystemItem
{
string Name { get; }
int GetSize();
}
// Класс листа
public class File : IFileSystemItem
{
public string Name { get; }
private int _size;
public File(string name, int size)
{
Name = name;
_size = size;
}
public int GetSize() => _size;
}
// Класс компоновщика
public class Folder : IFileSystemItem
{
public string Name { get; }
private List<IFileSystemItem> _items = new List<IFileSystemItem>();
public Folder(string name) => Name = name;
public void Add(IFileSystemItem item) => _items.Add(item);
public int GetSize() => _items.Sum(item => item.GetSize());
}Использование — клиенты работают с отдельными элементами и коллекциями единообразно:
var root = new Folder("root");
root.Add(new File("doc.txt", 100));
var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));
root.Add(subFolder);
Console.WriteLine(root.GetSize()); // 650Паттерн Компоновщик полезен для файловых систем, деревьев компонентов пользовательского интерфейса, организационных диаграмм или любых структур, где контейнеры и содержимое должны обрабатываться одинаково.
Попробуйте сами
using System;
using System.Collections.Generic;
using Organization;
class Program
{
public static void Main(string[] args)
{
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Создайте словарь для хранения отделов по именам
// Это поможет вам находить родительские отделы при добавлении компонентов
Dictionary<string, Department> departments = new Dictionary<string, Department>();
// TODO: Отслеживайте корневой отдел
Department root = null;
for (int i = 0; i < n; i++)
{
string type = Console.ReadLine();
if (type == "department")
{
string name = Console.ReadLine();
string parent = Console.ReadLine();
// TODO: Создайте отдел
// TODO: Добавьте его в словарь departments
// TODO: Если parent равен "root", то это корневой отдел
// TODO: В противном случае найдите родительский отдел и добавьте этот отдел в него
}
else if (type == "employee")
{
string name = Console.ReadLine();
int salary = Convert.ToInt32(Console.ReadLine());
string parent = Console.ReadLine();
// TODO: Создайте сотрудника
// TODO: Найдите родительский отдел и добавьте в него сотрудника
}
}
// TODO: Выведите общую зарплату корневого отдела
// Формат: "{root_name} Total Salary: {amount}"
Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов