Паттерн Компоновщик
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 99 из 110.
Паттерн Компоновщик (Composite) позволяет единообразно работать с отдельными объектами и группами объектов. Он объединяет объекты в древовидные структуры, где как отдельные элементы, так и коллекции элементов имеют общий интерфейс. Это идеально подходит для представления иерархий, таких как файловые системы, организационные структуры или компоненты пользовательского интерфейса.
У этого паттерна есть два типа участников: узлы Leaf (индивидуальные объекты без дочерних элементов) и узлы Composite (контейнеры, содержащие другие компоненты). Оба реализуют общий интерфейс Component:
abstract class FileSystemItem {
String get name;
int getSize();
void display([String indent = '']);
}
class File implements FileSystemItem {
@override
final String name;
final int size;
File(this.name, this.size);
@override
int getSize() => size;
@override
void display([String indent = '']) {
print('$indent- $name ($size KB)');
}
}
class Folder implements FileSystemItem {
@override
final String name;
final List<FileSystemItem> _children = [];
Folder(this.name);
void add(FileSystemItem item) => _children.add(item);
@override
int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());
@override
void display([String indent = '']) {
print('$indent+ $name/');
for (var child in _children) {
child.display('$indent ');
}
}
}
void main() {
var docs = Folder('Documents');
docs.add(File('resume.pdf', 150));
docs.add(File('photo.jpg', 2400));
var projects = Folder('Projects');
projects.add(File('main.dart', 25));
docs.add(projects);
docs.display();
print('Total: ${docs.getSize()} KB');
}Класс Folder может содержать как объекты File, так и другие объекты Folder. Когда вы вызываете getSize() для папки, он рекурсивно вычисляет общий размер всего её содержимого. Клиентскому коду не нужно знать, работает ли он с отдельным файлом или с целой иерархией папок — и те, и другие отвечают на одни и те же методы.
Задание
ЛегкоДавайте создадим систему организационной структуры, используя паттерн Компоновщик (Composite)! Вы создадите структуру, в которой как отдельные сотрудники, так и целые отделы (содержащие сотрудников и другие отделы) могут рассматриваться единообразно — это идеальный способ представления иерархии компании.
Вы организуете свой код в двух файлах:
organization.dart: Этот файл содержит интерфейс компонента, а также классы «листа» и «композита». Создайте абстрактный классOrganizationUnitс геттеромname, методомgetSalaryBudget(), который возвращаетint, и методомdisplay([String indent = ''])для отображения иерархии. Создайте классEmployee(лист), который реализуетOrganizationUnit. У него должны быть имя и зарплата, а методgetSalaryBudget()должен возвращать зарплату сотрудника. Его методdisplay()должен выводить[indent]- [name] ($[salary]). Затем создайте классDepartment(композит), который также реализуетOrganizationUnit. У него должно быть имя и список дочерних элементовOrganizationUnit. Добавьте методadd()для включения сотрудников или подотделов. Его методgetSalaryBudget()должен рекурсивно суммировать бюджеты всех дочерних элементов. Его методdisplay()должен выводить[indent]+ [name]/, после чего отображать каждого ребенка с увеличенным отступом (добавьте два пробела).main.dart: Импортируйте файл организации и постройте структуру компании. Создайте отделEngineering, содержащий двух сотрудников:Aliceс зарплатой80000иBobс зарплатой75000. Создайте отделQAс одним сотрудником:Carolс зарплатой65000. Теперь создайте отделTechnologyи добавьтеEngineeringиQAв качестве подотделов. Вызовитеdisplay()для отдела Technology, затем выведитеTotal Budget: $, а за ним — общий бюджет зарплат.
Прелесть паттерна Компоновщик заключается в том, что Technology.getSalaryBudget() автоматически рассчитывает общую сумму по всем вложенным отделам и сотрудникам — клиентскому коду не нужно знать внутреннюю структуру!
Ожидаемый вывод:
+ Technology/
+ Engineering/
- Alice ($80000)
- Bob ($75000)
+ QA/
- Carol ($65000)
Total Budget: $220000Шпаргалка
Паттерн Компоновщик (Composite) позволяет единообразно трактовать отдельные объекты и группы объектов, объединяя их в древовидные структуры, где как одиночные элементы, так и коллекции имеют общий интерфейс.
Паттерн состоит из:
- Component (Компонент): абстрактный интерфейс, определяющий общие операции
- Leaf (Лист): отдельные объекты, не имеющие дочерних элементов
- Composite (Компоновщик): контейнеры, содержащие другие компоненты (листья или другие компоновщики)
Пример: Файловая система
abstract class FileSystemItem {
String get name;
int getSize();
void display([String indent = '']);
}
class File implements FileSystemItem {
@override
final String name;
final int size;
File(this.name, this.size);
@override
int getSize() => size;
@override
void display([String indent = '']) {
print('$indent- $name ($size KB)');
}
}
class Folder implements FileSystemItem {
@override
final String name;
final List<FileSystemItem> _children = [];
Folder(this.name);
void add(FileSystemItem item) => _children.add(item);
@override
int getSize() => _children.fold(0, (sum, item) => sum + item.getSize());
@override
void display([String indent = '']) {
print('$indent+ $name/');
for (var child in _children) {
child.display('$indent ');
}
}
}
void main() {
var docs = Folder('Documents');
docs.add(File('resume.pdf', 150));
docs.add(File('photo.jpg', 2400));
var projects = Folder('Projects');
projects.add(File('main.dart', 25));
docs.add(projects);
docs.display();
print('Total: ${docs.getSize()} KB');
}Класс Folder может содержать как объекты File, так и другие объекты Folder. При вызове getSize() для папки он рекурсивно вычисляет общий размер всего её содержимого. Клиентский код работает с отдельными файлами и целыми иерархиями папок единообразно.
Попробуйте сами
import 'organization.dart';
void main() {
// TODO: Создайте отдел Engineering
// TODO: Добавьте Alice с зарплатой 80000
// TODO: Добавьте Bob с зарплатой 75000
// TODO: Создайте отдел QA
// TODO: Добавьте Carol с зарплатой 65000
// TODO: Создайте отдел Technology
// TODO: Добавьте Engineering и QA в качестве подотделов
// TODO: Вызовите display() для отдела Technology
// TODO: Выведите "Total Budget: $" и общий бюджет зарплат
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий