Паттерн «Компоновщик»
Часть раздела Объектно-ориентированное программирование путешествия по 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();
}
}composite делегирует операции своим элементам children, обеспечивая рекурсивные структуры. Клиенты взаимодействуют с деревом, не зная, работают ли они с leaf или с composite:
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 идеально подходит для представления иерархий, таких как организационные диаграммы, компоненты пользовательского интерфейса или системы меню, где необходимо одинаково обрабатывать группы и отдельных элементов.
Задание
ЛегкоДавайте создадим систему организационной диаграммы с использованием паттерна Composite! Вы создадите структуру, в которой departments могут содержать employees и другие sub-departments, что позволит вычислять общую сумму зарплат и единообразно отображать иерархию — независимо от того, просматриваете ли вы одного employee или целое подразделение.
Вы организуете код в четырёх файлах:
OrganizationComponent.java: Define интерфейс component, который будут реализовывать как employees, так и departments. Он должен объявлять два метода:showDetails(String indent)для отображения component с правильным отступом иgetSalary(), возвращающий общую зарплату в виде целого числа.Employee.java: Create leaf class, представляющий отдельных работников. УEmployeeесть имя (String) и зарплата (int), задаваемые через constructor. При вызовеshowDetailsон должен выводить[indent][name]: $[salary]. МетодgetSalaryпросто возвращает зарплату employee.Department.java: Create composite class, которая может содержать как employees, так и sub-departments. УDepartmentесть имя (String), а также список дочерних элементовOrganizationComponent. В нём должен быть методadd(OrganizationComponent component)для добавления участников. При вызовеshowDetailsон выводит[indent][name] Department, а затем вызываетshowDetailsдля каждого дочернего элемента с увеличенным отступом (добавляя два пробела). МетодgetSalaryвозвращает сумму зарплат всех дочерних элементов.Main.java: Build вашу организацию! Вы получите четыре входных значения: два имени employees с их зарплатами, а также имя sub-department с одним employee.Create department "Engineering" в качестве корневого. Add в него двух employees, используя первые две пары имя/зарплата. Затем создайте sub-department, используя третье входное значение, add в него одного employee, используя четвёртую пару имя/зарплата, и add этот sub-department в Engineering.
Вызовите
showDetails("")для department Engineering, затем выведите общую зарплату в форматеTotal Salary: $[amount].
Вы получите входные значения в следующем порядке: имя employee1 (String), зарплата employee1 (int), имя employee2 (String), зарплата employee2 (int), имя sub-department (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 без проблем работают независимо от того, вызываются ли они для одного employee или для целого department, содержащего вложенные sub-departments. Composite-структура автоматически обрабатывает рекурсию, позволяя работать со всей иерархией единообразно!
Попробуйте сами
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: Create the "Engineering" department as the root
// TODO: Создать и добавить двух сотрудников в Engineering, используя данные emp1 и emp2
// TODO: Создать подотдел, используя subDeptName
// TODO: Создать и добавить одного сотрудника в подотдел, используя данные emp3
// TODO: Добавить подотдел в Engineering
// TODO: Call showDetails("") on the Engineering department
// 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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java