Структурные паттерны
Структурные паттерны решают проблемы композиции классов и объектов, помогая создавать большие структуры из множества объектов.
Что такое структурные паттерны?
Структурные паттерны показывают, как собирать объекты и классы в более крупные структуры, сохраняя при этом гибкость и эффективность структуры.
Основные паттерны
- Adapter (Адаптер) — делает несовместимые интерфейсы совместимыми
- Bridge (Мост) — разделяет абстракцию и реализацию
- Composite (Компоновщик) — группирует объекты в древовидные структуры
- Decorator (Декоратор) — добавляет функциональность объектам динамически
- Facade (Фасад) — предоставляет упрощенный интерфейс к подсистеме
- Flyweight (Приспособленец) — экономит память через переиспользование
- Proxy (Заместитель) — контролирует доступ к объектам
Подробное описание паттернов
Adapter (Адаптер)
Позволяет взаимодействовать двум изначально несовместимым интерефейсам программы. Его главная задача - реализация нужного интерфейса и передача его вызовов адаптируемому объекту
Когда использовать:
- Когда код приложения уже написан, и внесение масштабных изменений в логику основного приложения для реализации конкретного функционала нецелесообразно;
- Когда требуется использовать сторонний класс, интерфейс которого несовместим с приложением;
- Когда требуется расширить функционал подклассов приложения.
Bridge (Мост)
Разделяет абстракцию и реализацию так, чтобы они могли изменяться независимо.
Когда использовать:
- Когда нужно избежать постоянной привязки абстракции к реализации
- Когда абстракции и реализации должны расширяться независимо
- Когда изменения в реализации не должны влиять на клиентский код
- Когда нужно скрыть детали реализации от клиентов
Composite (Компоновщик)
Позволяет сгруппировать объекты в древовидные структуры, а затем работать с ними так, как будто это единичные объекты.
Когда использовать:
- Когда нужно представить иерархии объектов "часть-целое"
- Когда клиенты должны единообразно трактовать составные и индивидуальные объекты
- Когда структура может иметь любую сложность и динамически изменяться
- Когда нужно работать с древовидными структурами
Decorator (Декоратор)
Позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные "обертки".
Когда использовать:
- Когда нужно добавить обязанности к отдельным объектам динамически и прозрачно
- Когда расширение путем порождения подклассов становится непрактичным
- Когда нужно добавить функциональность, которую можно отменить
- Когда хотите избежать создания множества подклассов для каждой комбинации
Facade (Фасад)
Предоставляет упрощенный интерфейс к сложной подсистеме классов, библиотеке или фреймворку.
Когда использовать:
- Когда нужно предоставить простой интерфейс к сложной подсистеме
- Когда хотите разделить клиентов и подсистемы на слои
- Когда нужно скрыть сложность подсистемы от клиентского кода
- Когда хотите создать точку входа в каждый уровень многоуровневой архитектуры
Flyweight (Приспособленец)
Позволяет вместить больше объектов в доступную оперативную память за счет общего переиспользования одинаковых частей состояния между множеством объектов.
Когда использовать:
- Когда приложение использует большое количество объектов
- Когда хранение объектов обходится дорого из-за их большого количества
- Когда большинство состояний объектов можно сделать внешними
- Когда нужно экономить память в приложениях с ограниченными ресурсами
Proxy (Заместитель)
Предоставляет объект-заместитель или заполнитель для другого объекта, чтобы контролировать доступ к нему.
Когда использовать:
- Когда нужен более универсальный или сложный указатель на объект, чем простой указатель
- Когда нужно контролировать доступ к объекту
- Когда нужна ленивая инициализация дорогих объектов
- Когда нужно кэширование результатов запросов
- Когда нужно логирование доступа к объекту
Примеры использования
Adapter
class OldPrinter {
printOld(text) {
console.log(`Old format: ${text}`);
}
}
class PrinterAdapter {
constructor(oldPrinter) {
this.oldPrinter = oldPrinter;
}
print(text) {
this.oldPrinter.printOld(text);
}
}
Decorator
class Coffee {
cost() {
return 5;
}
}
class MilkDecorator {
constructor(coffee) {
this.coffee = coffee;
}
cost() {
return this.coffee.cost() + 2;
}
}
Преимущества
- Улучшают структуру кода
- Повышают гибкость системы
- Упрощают взаимодействие между компонентами
- Способствуют переиспользованию кода
Недостатки
- Могут усложнить понимание кода
- Некоторые паттерны могут снизить производительность
- Требуют дополнительного кода
Структурные паттерны помогают создавать элегантные и гибкие архитектуры!