Композитный паттерн: примеры решений для иерархий «часть-целое

Динамические структуры данных требуют четкой и лаконичной иерархической структуры. Реализовать эти структуры на практике зачастую не так просто. Необходимо следить за тем, чтобы тип объекта не запрашивался каждый раз перед обработкой фактических данных, поскольку это было бы неэффективно. Использование композитного паттерна проектирования рекомендуется, когда многие примитивные объекты встречаются с составными объектами. Этот подход к проектированию программного обеспечения позволяет клиентам последовательно обрабатывать отдельные и составные объекты, скрывая их различия от клиента.

Что такое композитный шаблон?

Композитный паттерн проектирования (сокращенно композитный паттерн) — это один из 23 паттернов проектирования GoF для разработки программного обеспечения, опубликованный Эрихом Гамма, Ричардом Хелмом, Ральфом Джонсоном и Джоном Влиссидесом (совместно называемыми «бандой четырех») в 1994 году. Как и паттерн фасада и паттерн декоратора, это паттерн проектирования, который объединяет объекты и классы в более крупные структуры.

Композитный паттерн следует основной идее представления простых объектов и их контейнеров или композиций (т.е. композиций объектов) в абстрактном классе, чтобы их можно было обрабатывать единообразно. Такой тип структуры называется иерархией «часть-целое», при которой объект является либо только частью целого, либо целым, состоящим из отдельных частей.

Какие проблемы можно решить с помощью композитного паттерна UML?

Основной целью композитного паттерна проектирования является — как и всех паттернов GoF — наилучшее решение повторяющихся проблем проектирования в объектно-ориентированной разработке. Желаемый конечный результат — гибкое программное обеспечение, характеризующееся легко реализуемыми, тестируемыми, взаимозаменяемыми и многократно используемыми объектами. Для этого паттерн Composite описывает способ, с помощью которого отдельные объекты и составные объекты могут рассматриваться одинаково. Таким образом, может быть создана структура объектов, которая проста для понимания и обеспечивает высокоэффективный клиентский доступ. Кроме того, минимизируется подверженность кода ошибкам.

Композитный паттерн проектирования: графическая нотация (UML)

Для реализации иерархий «часть-целое» паттерн Composite предусматривает реализацию единого компонентного интерфейса для простых объектов-частей, также называемых листовыми объектами, и составных объектов. Отдельные листовые объекты непосредственно интегрируют этот интерфейс; составные объекты автоматически направляют специфические требования клиента к интерфейсу и подчиненным компонентам. Для клиента не имеет значения, какого типа объект (часть или целое), поскольку он обращается только к интерфейсу.

Следующая диаграмма классов с использованием графической нотации UML более четко визуализирует связи и иерархии программного обеспечения Composite Pattern.

Сильные и слабые стороны композитного паттерна

Композитный паттерн — это константа в разработке программного обеспечения. Проекты с сильно вложенными структурами, как правило, выигрывают от практического подхода, который заключается в объектах: будь то примитивный объект или составной объект, имеет ли он простые или сложные зависимости. Глубина и ширина вложенности не имеет значения в композитном паттерне проектирования. Различия между типами объектов могут быть проигнорированы клиентом, так что для запроса не требуется отдельных функций. Преимуществом этого является то, что клиентский код является простым и компактным.

Еще одним преимуществом композитного паттерна является гибкость и простота расширения, которые он дает программному обеспечению. Универсальный компонентный интерфейс позволяет интегрировать новые листы и составные объекты без изменений в коде — как на стороне клиента, так и в существующих структурах объектов.

Но, несмотря на все свои преимущества, паттерн Composite и его универсальный интерфейс имеют некоторые недостатки. Интерфейс может быть мучением для разработчиков. Реализация сопряжена с рядом серьезных проблем. Например, необходимо заранее решить, какие операции должны быть определены в интерфейсе, а какие — в составных классах. Последующая настройка свойств составных классов (например, ограничение дочерних элементов) часто бывает сложной и труднореализуемой.

Преимущества Недостатки
Обеспечивает все для отображения сильно вложенных структур объектов Реализация интерфейсов компонентов очень сложна
Бережливый, простой для понимания программный код Последующая настройка составных функций сложна и громоздка для реализации
Большая расширяемость  

Области применения композитного паттерна

Использование композитного паттерна оправдывает себя там, где необходимо выполнять операции над динамическими структурами данных со сложной иерархией (по глубине и ширине). Такую структуру также называют двоичной древовидной структурой, которая интересна для широкого спектра программных сценариев и часто используется. Типичными примерами являются:

Системы данных: Системы данных являются одними из самых важных компонентов программного обеспечения устройства. Их можно легко отобразить с помощью составного шаблона. Отдельные файлы отображаются в виде листовых объектов или папок, которые могут содержать собственные данные или вложенные папки.

Программные меню: Программные меню представляют собой типичные случаи использования двоичной древовидной структуры в соответствии с композитным шаблоном проектирования. Программные меню представляют собой типичный вариант использования бинарной древовидной структуры в соответствии с композитным шаблоном проектирования. Строка меню содержит один или несколько корневых пунктов (составных объектов), таких как «Файл». Они обеспечивают доступ к различным пунктам меню, которые могут быть либо нажаты напрямую (лист), либо содержать дополнительные подменю (составной объект).

Графический интерфейс (GUI): Древовидные структуры и композитный паттерн также играют важную роль в разработке графических пользовательских интерфейсов. Помимо простых листовых элементов, таких как кнопки, текстовые поля или флажки, составные контейнеры, такие как рамки или панели, обеспечивают четкую структуру и большую наглядность.

Пример кода: Композитный паттерн

Паттерн «Композит», вероятно, лучше всего проявился в языке программирования Java. Среди прочего, этот паттерн лежит в основе инструментария абстрактных окон (AWT). AWT — это практичный и популярный API с около 50 готовыми к использованию классами Java для разработки кросс-платформенных интерфейсов Java. В следующем примере кода, взятом из записи блога «Композитный паттерн проектирования в Java» на сайте baeldung.com, мы сосредоточились на популярном языке программирования.

В этом практическом примере показана иерархическая структура отделов компании. В качестве первого шага определяется компонентный интерфейс «Отдел»:

public interface Department {
	void printDepartmentName();
}

Затем определяются два простых листовых класса «FinancialDepartment» (для финансового отдела) и «SalesDepartment» (для отдела продаж). Оба реализуют метод printDepartmentName() из интерфейса компонента, но не содержат никаких других объектов Department.

public class FinancialDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}
public class SalesDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}

Составной класс, который соответствует иерархии, определен как «HeadDepartment». Он состоит из нескольких компонентов отдела и, помимо метода printDepartmentName(), содержит методы для добавления новых элементов (addDepartment) или удаления существующих элементов (removeDepartment):

public class HeadDepartment implements Department {
	private Integer id;
	private String name;
	private List<department> childDepartments;</department>
	public HeadDepartment(Integer id, String name) {
		this.id = id;
		this.name = name;
		this.childDepartments = new ArrayList<>();
	}
	public void printDepartmentName() {
		childDepartments.forEach(Department::printDepartmentName);
	}
	public void addDepartment(Department department) {
		childDepartments.add(department);
	}
	public void removeDepartment(Department department) {
		childDepartments.remove(department);
	}
}

Оцените статью
cdelat.ru
Добавить комментарий