В этой серии мы рассмотрим шаблоны структурного проектирования:
- Композитный
- Адаптер
- Мост
- Декоратор
- Фасад
- Наилегчайший вес
- Прокси
🤓 Что это?
Композитный – это структурный шаблон проектирования, который позволяет объединять объекты в древовидные структуры, а затем работать с этими структурами, как если бы они были отдельными объектами.
TL;DR: шаблон Composite позволяет клиентам одинаково обрабатывать отдельные объекты и композиции объектов.
🥰 Почему..
Представьте, что у вас есть набор объектов, которые можно организовать в древовидную структуру, где некоторые объекты действуют как «листья», а другие — как «ветви». Теперь предположим, что вы хотите выполнить какую-то операцию со всеми этими объектами, но не хотите писать отдельный код для каждого типа объекта. Вот где шаблон проектирования Composite пригодится!
Шаблон Composite позволяет обрабатывать отдельные объекты и группы объектов одинаково. Это означает, что вы можете написать один набор кода, который может обрабатывать как отдельные объекты, так и группы объектов, делая ваш код более простым и более модульным.
Кроме того, шаблон Composite упрощает динамическое добавление или удаление объектов из древовидной структуры без необходимости изменения кода, обрабатывающего дерево. Это делает ваш код более гибким и простым в обслуживании в долгосрочной перспективе.
На примерах станет понятнее ❤️
🥰 Структура
Составной шаблон состоит из трех основных типов объектов:
- Компонент
Компонент объявляет интерфейс для объектов в композиции и при необходимости реализует поведение по умолчанию для интерфейса, общего для всех классов. Класс базового компонента может объявить интерфейс для доступа к своим дочерним компонентам и управления ими. - Лист
Лист представляет конечные объекты композиции. Лист не имеет потомков; он определяет поведение примитивных объектов в композиции. - Composite
Composite определяет поведение компонентов, имеющих дочерние элементы; он хранит дочерние компоненты и реализует операции, связанные с дочерними компонентами, в интерфейсе компонентов.
😎 Примеры:
🚙 Пример 1
Давайте рассмотрим пример файловой системы, в которой у нас есть файлы и каталоги. Каталоги могут содержать файлы, а также другие каталоги. Мы можем реализовать Composite Design Pattern для представления этой иерархии файловой системы.
Здесь составной шаблон говорит о том, что мы можем работать с файлами и каталогами через общий интерфейс.
interface FileSystemComponent { getName(): string; getSize(): number; addChild(child: FileSystemComponent): void; } class File implements FileSystemComponent { private name: string; private size: number; constructor(name: string, size: number) { this.name = name; this.size = size; } getName() { return this.name; } getSize() { return this.size; } addChild(child: FileSystemComponent) { throw new Error('Cannot add child to a file'); } } class Directory implements FileSystemComponent { private name: string; private children: FileSystemComponent[] = []; constructor(name: string) { this.name = name; } getName() { return this.name; } getSize() { let size = 0; for (const child of this.children) { size += child.getSize(); } return size; } addChild(child: FileSystemComponent) { this.children.push(child); } }
Теперь мы можем использовать составной шаблон проектирования для обхода иерархии файловой системы и выполнения операций с файлами и каталогами, как если бы они были отдельными объектами. Например, мы можем рассчитать общий размер каталога и всех его подкаталогов следующим образом:
function getTotalSize(component: FileSystemComponent): number { let size = component.getSize(); if (component instanceof Directory) { for (const child of component.children) { size += getTotalSize(child); } } return size; } // Client const root = new Directory('root'); const folder1 = new Directory('folder1'); const folder2 = new Directory('folder2'); root.addChild(folder1); root.addChild(folder2); folder1.addChild(new File('file1', 100)); folder1.addChild(new File('file2', 200)); folder2.addChild(new File('file3', 50)); console.log(getTotalSize(root));
👨🏻💻 Пример 2:
Представьте, что вы создаете интернет-магазин. Магазин продает товары и услуги. Каждый продукт или услуга может продаваться по отдельности или в составе пакета. Например, клиент может купить книгу, музыкальный компакт-диск и видео-DVD в комплекте. Покупатель также может приобрести каждый из этих предметов по отдельности.
Здесь составной шаблон говорит о том, что мы можем работать с продуктами и наборами через один общий интерфейс.
import java.util.ArrayList; import java.util.List; // Component public abstract class Product { public abstract double getPrice(); } // Leaf public class Book extends Product { private String title; private double price; public Book(String title, double price) { this.title = title; this.price = price; } public double getPrice() { return price; } } // Leaf public class MusicCD extends Product { private String title; private double price; public MusicCD(String title, double price) { this.title = title; this.price = price; } public double getPrice() { return price; } } // Leaf public class VideoDVD extends Product { private String title; private double price; public VideoDVD(String title, double price) { this.title = title; this.price = price; } public double getPrice() { return price; } } // Composite public class Bundle extends Product { private String name; private List<Product> products = new ArrayList<>(); public Bundle(String name, Product... products) { this.name = name; for (Product product : products) { this.products.add(product); } } public void addProduct(Product product) { products.add(product); } public void removeProduct(Product product) { products.remove(product); } public double getPrice() { double totalPrice = 0; for (Product product : products) { totalPrice += product.getPrice(); } return totalPrice; } }
Теперь мы для клиентского кода
// Client code public class OnlineStore { public static void main(String[] args) { Book book = new Book("The Lord of the Rings", 20.0); MusicCD musicCD = new MusicCD("Abbey Road", 15.0); VideoDVD videoDVD = new VideoDVD("The Godfather", 25.0); Bundle bundle = new Bundle("Entertainment Bundle", book, musicCD, videoDVD); System.out.println("Total price: " + bundle.getPrice()); } }
🧐 Когда использовать?
- Когда у вас есть коллекция объектов, которые можно структурировать в виде древовидной иерархии.
- Когда вы хотите иметь возможность работать как с отдельными объектами, так и с группами объектов согласованным образом.
- Когда вам нужно иметь возможность динамически добавлять или удалять объекты, не затрагивая общую структуру дерева.
- Если вы хотите упростить клиентский код, предоставив унифицированный способ работы со сложными объектными структурами.
- Когда вам нужно повысить возможность повторного использования и ремонтопригодность кода, предоставив модульное и расширяемое решение.
✅ Преимущества
- Упрощает клиентский код, единообразно обрабатывая отдельные объекты и группы объектов.
- Позволяет динамически добавлять или удалять объекты из древовидной структуры, не затрагивая общую структуру дерева.
- Обеспечивает четкий и последовательный способ работы со сложными объектными структурами.
- Повышает возможность повторного использования кода и удобство сопровождения за счет модульного и расширяемого решения.
❌ Недостатки
- Может усложнить реализацию самого шаблона Composite.
- Для реализации могут потребоваться дополнительные усилия по сравнению с более простыми шаблонами проектирования.
- Может не потребоваться для более простых структур объектов, не имеющих иерархической древовидной организации.
🚀 Заключение
Composite Design Pattern — это мощный инструмент для представления древовидных иерархий объектов. Используя классы и интерфейсы, мы можем создавать составные и конечные объекты, которые имеют общий интерфейс, и работать с ними, как если бы они были отдельными объектами. Этот шаблон может быть полезен в различных приложениях, таких как файловые системы, компоненты графического интерфейса пользователя и организационные иерархии.
Дополнительные пояснения и примеры кода можно найти в моем репозитории:
Надеюсь, эта статья была для вас интересной и веселой ❤️