Паттерн «Компоновщик»
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 75 из 87.
Паттерн Компоновщик (Composite Pattern) — это структурный паттерн проектирования, который позволяет группировать объекты в древовидные структуры для представления иерархий «целое-часть». Он позволяет клиентам единообразно работать как с отдельными объектами, так и с их группами через общий интерфейс.
Представьте себе файловую систему: папка может содержать файлы и другие папки, которые, в свою очередь, могут содержать еще больше файлов и папок. Независимо от того, имеете ли вы дело с отдельным файлом или со всей структурой папок, вы хотите выполнять такие операции, как «получить размер», одинаковым способом. Паттерн определяет интерфейс Component, объекты Leaf (отдельные элементы) и объекты Composite (контейнеры, содержащие дочерние элементы):
interface FileComponent {
void display(String indent);
int getSize();
}
class File implements FileComponent {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void display(String indent) {
System.out.println(indent + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
class Folder implements FileComponent {
private String name;
private List<FileComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
public void display(String indent) {
System.out.println(indent + name + "/");
for (FileComponent child : children) {
child.display(indent + " ");
}
}
public int getSize() {
return children.stream().mapToInt(FileComponent::getSize).sum();
}
}Компоновщик делегирует операции своим дочерним элементам, позволяя создавать рекурсивные структуры. Клиенты взаимодействуют с деревом, не зная, работают ли они с листом или с компоновщиком:
Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));
Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);
root.display("");
System.out.println("Total: " + root.getSize() + "KB");Паттерн «Компоновщик» (Composite Pattern) идеально подходит для представления иерархий, таких как организационные структуры, UI-компоненты или системы меню, где необходимо одинаково работать как с группами, так и с отдельными объектами.
Задание
ЛегкоДавайте создадим систему организационной структуры, используя паттерн Компоновщик (Composite Pattern)! Вы создадите структуру, в которой отделы могут содержать сотрудников и другие подотделы, что позволит вам рассчитывать общую заработную плату и отображать иерархию единообразно — независимо от того, смотрите ли вы на одного сотрудника или на целое подразделение.
Вы организуете свой код в четырех файлах:
OrganizationComponent.java: Определите интерфейс компонента, который будут реализовывать как сотрудники, так и отделы. Он должен объявлять два метода:showDetails(String indent)для отображения компонента с соответствующим отступом иgetSalary(), который возвращает общую зарплату в виде целого числа.Employee.java: Создайте класс «листа», представляющий отдельных работников. УEmployeeесть имя (String) и зарплата (int), которые устанавливаются через конструктор. При вызовеshowDetailsон должен выводить[indent][name]: $[salary]. МетодgetSalaryпросто возвращает зарплату сотрудника.Department.java: Создайте составной класс (композит), который может содержать как сотрудников, так и подотделы. УDepartmentесть имя (String), и он хранит список дочерних элементовOrganizationComponent. У него должен быть методadd(OrganizationComponent component)для добавления участников. При вызовеshowDetailsон выводит[indent][name] Department, а затем вызываетshowDetailsдля каждого дочернего элемента с увеличенным отступом (добавьте два пробела). МетодgetSalaryвозвращает сумму зарплат всех дочерних элементов.Main.java: Постройте свою организацию! Вы получите входные данные: имена двух сотрудников с их зарплатами, а также название подотдела с одним сотрудником.Создайте отдел "Engineering" в качестве корневого. Добавьте в него двух сотрудников, используя первые две пары имя/зарплата. Затем создайте подотдел, используя третий входной параметр, добавьте в него одного сотрудника, используя четвертую пару имя/зарплата, и добавьте этот подотдел в Engineering.
Вызовите
showDetails("")для отдела Engineering, затем выведите общую зарплату в форматеTotal Salary: $[amount].
Вы получите входные данные в следующем порядке: имя employee1 (String), зарплата employee1 (int), имя employee2 (String), зарплата employee2 (int), название подотдела (String), имя employee3 (String), зарплата employee3 (int).
Например, при входных данных Alice, 75000, Bob, 65000, QA, Charlie, 55000, ваш вывод будет следующим:
Engineering Department
Alice: $75000
Bob: $65000
QA Department
Charlie: $55000
Total Salary: $195000Обратите внимание, как одни и те же методы showDetails и getSalary бесперебойно работают независимо от того, вызываются ли они для одного сотрудника или для целого отдела, содержащего вложенные подотделы. Составная структура обрабатывает рекурсию автоматически, позволяя вам единообразно работать со всей иерархией!
Шпаргалка
Паттерн Компоновщик (Composite) — это структурный паттерн проектирования, который объединяет объекты в древовидные структуры для представления иерархий «целое-часть». Он позволяет единообразно трактовать как отдельные объекты, так и их совокупности через общий интерфейс.
Паттерн состоит из трех ключевых элементов:
- Component (Компонент): Интерфейс, определяющий общие операции
- Leaf (Лист): Отдельные объекты, реализующие интерфейс компонента
- Composite (Композит): Объекты-контейнеры, которые содержат дочерние элементы и делегируют им выполнение операций
Пример реализации файловой системы:
// Интерфейс компонента
interface FileComponent {
void display(String indent);
int getSize();
}
// Класс «Лист»
class File implements FileComponent {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void display(String indent) {
System.out.println(indent + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
// Класс «Композит»
class Folder implements FileComponent {
private String name;
private List<FileComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
public void display(String indent) {
System.out.println(indent + name + "/");
for (FileComponent child : children) {
child.display(indent + " ");
}
}
public int getSize() {
return children.stream().mapToInt(FileComponent::getSize).sum();
}
}Использование структуры компоновщика:
Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));
Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);
root.display("");
System.out.println("Total: " + root.getSize() + "KB");Композит рекурсивно делегирует операции своим дочерним элементам. Клиенты взаимодействуют с деревом, не зная, работают ли они с листом или с композитом.
Случаи использования: Организационные диаграммы, компоненты пользовательского интерфейса, системы меню, файловые системы — любая иерархия, где группы и отдельные объекты требуют одинакового обращения.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Чтение входных данных
String emp1Name = scanner.nextLine();
int emp1Salary = Integer.parseInt(scanner.nextLine());
String emp2Name = scanner.nextLine();
int emp2Salary = Integer.parseInt(scanner.nextLine());
String subDeptName = scanner.nextLine();
String emp3Name = scanner.nextLine();
int emp3Salary = Integer.parseInt(scanner.nextLine());
// TODO: Создайте отдел "Engineering" в качестве корневого
// TODO: Создайте и добавьте двух сотрудников в Engineering, используя данные emp1 и emp2
// TODO: Создайте подотдел, используя subDeptName
// TODO: Создайте и добавьте одного сотрудника в подотдел, используя данные emp3
// TODO: Добавьте подотдел в Engineering
// TODO: Вызовите showDetails("") для отдела Engineering
// TODO: Выведите общую зарплату в формате: Total Salary: $[amount]
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»