Структурные паттерны: адаптер, компоновщик, декоратор, заместитель, мост, фасад, приспособленец. - Painted-Black/BMSTU-OOP GitHub Wiki
В структурных паттернах рассматривается вопрос о том, как из классов и объектов образуются более крупные структуры.
Структурные паттерны уровня класса используют наследование для составления композиций из интерфейсов и реализаций. Структурные паттерны уровня объекта компонуют объекты для получения новой функциональности.
Адаптер (Wrapper - обертка)
Адаптер - паттерн, структурирующий классы и объекты.
Преобразует интерфейс одного класса в интерфейс другого, который ожидают клиенты. Адаптер обеспечивает совместную работу классов с несовместимыми интерфейсами, которая без него была бы невозможна.
Применяйте паттерн адаптер, когда:
-
хотите использовать существующий класс, но его интерфейс не соответствует вашим потребностям;
-
собираетесь создать повторно используемый класс, который должен взаимодействовать с заранее неизвестными или не связанными с ним классами, имеющими несовместимый интерфейсы;
-
(только для адаптеров объектов!) нужно использовать несколько существующих подклассов, но непрактично адаптировать их интерфейсы путем плрождения новых подклассов от каждого. В этом случае адаптер объектов может приспосабливать интерфейс их общего родительского класса.
Адаптер класса использует множественное наследование для адаптации одного интерфейса к другому.
Адаптер объекта применяет композицию объектов.
Участники:
-
Target - целевой:
определяет зависящий от предметной области интерфейс, которым пользуется Client
-
Client - клиент
вступает во взаимоотношения с объектами удовлетворяющими интерфейсу Target
-
Adaptee - адаптируемый
определяет существующий интерфейс, нуждающийся в адаптации
-
Adapter - адаптер
адаптирует интерфейс Adaptee к интерфейсу Target.
Клиенты вызывают операции экземпляра адаптера Adapter. В свою очередь адаптер вызывает операции адаптируемого объекта или класса Adaptee, который и выполняет запрос.
Результаты применения адаптеров.
Адаптер класса:
-
Адаптирует Adaptee к Target, перепоручая действия конкретному классу Adaptee. Поэтому данный паттерн не будет работать, если мы захотим одновременно адаптировать класс и его подклассы;
-
Позволяет адаптеру Adapter заместить некоторые операции адаптируемого класса Adaptee, так как Adapter есть не что иное, как подкласс Adaptee;
-
Вводит только один новый объект. Чтобы добраться до адаптируемого класса, не нужно никакого дополнительноо обращения по указателю.
Адаптер объектов:
-
Позволяет одному адаптеру Adapter работать со многими адаптируемыми объектами Adaptee, то есть с самим Adaptee и его подклассами (если они есть). Адаптер может добавить новую функциональность сразу всем адаптируемым объектам;
-
Затрудняет перемещение операций класса Adaptee. Для этого потребуется породить от Adaptee подкласс и заставить Adapter ссылаться на этот подкласс, а не на сам Adaptee.
Пример
class FahrenheitSensor
{
public:
float getFahrenheitTemperature() { float t = 32.0; return t; }
};
class Sensor
{
public:
virtual ~Sensor() {}
virtual floar getTemperature() = 0;
};
class Adapter: public Sensor
{
public:
Adapter(FahrenheitSensor *p) : p_fsensor(p) {}
~Adapter() { delete p_fsensor;
float getTemperature()
{
return (p_fsensor->getFahrenheitTemperature() - 32.0) * 5.0 / 9.0;
}
private:
FahrenheitSensor *p_fsensor;
};
Мост (Bridge или Handle/Body)
Мост - паттерн, структурирующий объекты.
Назначение
Отделить абстракцию от ее реализации так, чтобы то и другое можно было изменять независимо.
Применимость
Используйте паттерн мост, когда:
-
хотите избежать постоянной привязки абстракции к реализации. Так, например, бывает, когда реализацию необходимо выбирать во время выполнения программы;
-
и абстракции, и реализации должны расширяться новыми подклассами. В таком случае паттерн мост позволяет комбинировать разные абстракции и реализации и изменять их независимо;
-
изменения в реализации абстракции не должны сказываться на клиентах, то есть клиентский код не должен перекомпилироваться;
-
(только для C++) вы хотите полностью скрыть от клиентов реализацию абстракции. В C++ представление класса видимо через его интерфейс;
-
число классов начинает быстро расти. Это признак того, что иерархию следует разделить на две части;
-
вы хотите разделить одну реализацию между несколькими объектами, и этот факт необходимо скрыть от клиента.
Участники
-
Abstraction - абстракция:
- определяет интерфейс абстракции
- хранит ссылку на объект типа Implementor
-
RefinedAbstraction - уточненная абстракция:
- расширяет интерфейс, определенный абстракцией Abstraction
-
Implementor - реализатор
- определяет интерфейс классов для реализации. Он не обязан точно соответствовать интерфейсу класса Abstraction. На самом деле оба интерфейса могут быть совершенно различны. Обычно интерфейс класса Implementor предоставляет только примитивные операции, а класс Abstraction определяет операции более высокого уровня, базирующиеся на этих примитивах
-
ConcreteImplementor - конктретный реализатор
- содержит конкретную реализацию интерфейса класса Implementor.
Класс Absraction перенаправляет своему объекту Implementor запросы клиента.
Результаты применения паттерна мост
-
отделение реализации от интерфейса. Реализация больше не имеет постоянной привязки к интерфейсу. Реализацию абстракции можно конфигурировать во время выполнения. Объект может даже динамически изменять свою реализацию.
-
Повышение степени расширяемости. Можно расширять независимо иерарзии классов Abstraction и Implementor.
-
Сокрытие деталей реализации от клиентов.
Компоновщик (Composite)
Компоновщик компонует объекты в древовидные структуры для представления иерархий часть-целое. Позволяет клиентам единообразно трактовать индивидуальные и составные объекты.
Используйте паттерн компоновщик, когда:
-
нужно представить иерархию объектов вида часть-целое;
-
хотите, чтобы клиенты единообразно трактовали составные и индивидуальные объекту.
Участники
-
Component - компонент
- объявляет интерфейс для компонуемых объектов
- предоставляет подходящую реализацию операций по умолчанию, общую для всех классов
- объявляет интерфейс для доступа к потомкам и управления ими
- определяет интерфейс для доступа к родителю компонента в рекурсивной структуре и при необходимости реализует его
-
Leaf - лист
- представляет листовые узлы композиции и не имеет потомков
- определяет поведение примитивных объектов в композиции
-
Composite
- определяет поведение компонентов, у которых есть потомки
- хранит компоненты-потомки
- реализует относящиеся к управлению потомками операции в интерфейсе класса Component
-
Client - клиент
- манипулирует объектами композиции через интерфейс Component.
Клиенты используют интерфейс класса Component для взаимодействия с объектами в составной структуре. Если получателем запроса является листовой объект Leaf, то он и обрабатывает запрос. Когда же получателем является составной объект Composite, то обычно он перенаправляет запрос своим потомкам, возможно, выполняя некоторые дополнительные операции до или после перенаправления.
Результаты
Паттерн компоновщик:
-
определяет иерархии классов, состоящие из примитивных и составных объектов. Из примитивных объектов можно составлять более сложные, которые, в свою очередь, участвуют в более сложных композициях и так далее. Любой клиент, ожидающий примитивного объекта, может работать и с составными;
-
упрощает архитектуру клиента. Клиенты могут единообразно работатьс индивидуальными объектами и с составными структурами. Обычно клиент не знает, взаимодействует ли он с листовым или составным объектом;
-
упрощает добавление новых видов компонентов. Новые подклассы классов Composite или Leaf будут автоматически работать с уже существующими структурами и клиентских кодом. Изменять клиента при добавлении новых компонентов не нужно. Однако такая простота добавления новых компонентов имеет и свои отрицательные стороны: становится трудно наложить органичения на то, какие объекты могут входить в состав композиции.
-
способствует созданию общего дизайна.
Декоратор (Decorator)
Динамически добавляет объекту новые обязанности. Является гибкой альтернативой порождению подклассов с целью расширения функциональности.
Используйте паттерн декоратор:
-
для динамического, прозрачного для клиентов добавления обязанностей объектам;
-
для реализации обязанностей, которые могут быть сняты с объекта;
-
когда расширение путем порождения подклассов по каким-то причинам неудобно или невозможно.
Участники:
-
Component - компонент
- определяет интерфейс для объектов, на которые могут быть возложеы дополнительные обязанности
-
ConcreteComponent - конкретный компонент
- определяет объект, на который возлагаются дополнительные обязанности
-
Decorator - декоратор
- хранит ссылку на объект Component и определяет интерфейс, соответствующий интерфейсу Component
-
ConcreteDecorator - конкретный декоратор
- возлагает дополнительные обязанности на компонент.
Decorator переадресует запросы объекту Component. Может выполнять и дополнительные операции до и после переадресации.
Результаты
-
большая гибкость, нежели у статического (множественного) наследования
-
позволяет избежать перегруженных функциями классов на верних уровнях иерархии
-
декоратор и его компонент не идентичны
-
множество мелких объектов.
Фасад (Facade)
Предоставляет унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы. Фасад определяет интерфейс более высокого уровня, который упрощает использование подсистемы.
Разбиение на подсистемы облегчает проектирование сложной системы в целом. Общая цель всякого проектирования - свести к минимуму зависимость подсистем друг от друга и обмен информацией между ними. Один из способов решения этой задачи - введение объекта фасад, предоставляющий единый упрощенный интерфейс к более сложным системным средствам.
Используйте фасад, когда:
-
хотите предоставить простой интерфейс к сложной подсистеме. Фасад предлагает некоторый вид системы по умолчанию, устраивающий большинство клиентов. И лишь те объекты, которым нужны более широкие возможности настройки, могут обратиться напрямую к тому, что находится за фасадом;
-
между клиентами и классами реализации абстракции существует много зависимостей. Фасад позволит отделить подсистему как от клиентов, так и от других подсистем, что, в свою очередь, способствует повышению степени независимости и переносимости;
-
вы хотите разложить подсистему на отдельные слои. Используйте фасад для определения точки входа на каждый уровень подсистемы. Если подсистемы зависят друг от друга, то зависисмость можно упросить, разрешив подсистемам обмениваться информацией только через фасады.
Участники
-
Facade - фасад
- "знает", каким классам подсистемы адресовать запрос
- делегирует запросы клиентов подходящим объектам внутри подсистемы
-
Классы подсистемы
- реализуют функциональность подсистемы
- выполняют работу, порученную объектом Facade
- ничего не знают о существовании фасада, то есть не хранят ссылок на него.
Клиенты общаются с подсистемой, посылая запросы фасаду. Он переадресует их подходящим объектам внутри подсистемы. Хотя основную работы выполняют именно объекты подсистемы, фасаду, возможно, придется преобразовать свой интерфейс в интерфейсы подсистемы.
Клиенты, пользующиеся фасадом, не имеют прямого доступа к объектам подсистемы.
Преимущества
-
изолирует клиентов от компонентов подсистемы, уменьшая тем самым число объектов, с которыми клиентам приходится иеть дело, и упрощая работу с подсистемой;
-
позволяет ослабить связанность между подсистемой и ее клиентами. Фасад может также упростить процесс переноса системы на другие платформы, поскольку уменьшается вероятность того, что в результате изменения одной подсистемы понадобится изменять и все остальные;
-
фасад не препятствует приложениям напрямую обращаться к классам подсистемы, если это необходимо. Таким образом, у вас есть выбор между простотой и общностью.
Приспособленец (Flyweight)
Использует разделение для эффективной поддержки множества мелких объектов.
Паттерн приспособленец показывает, как разделять очень мелкие объекты без недопустимо высоких издержек.
Приспособленец - это разделяемый объект, который можно использовать одновременно в нескольких контекстах. В каждом контексте он выглядит как независимый объект, то есть неотличим от экземпляра, который не разделяется. Приспособленцы не могут делать предположений о контексте, в котором работают. Приспособленцы моделируют концепции или сущности, число которых слишком велико для предстваления объектами.
Эффективность паттерна приспособленец во многом зависит от того, как и где он используется. Применяйте этот паттерн, когда выполнены все нижеперечисленные условия:
- в приложении используется большое число объектов
- из-за этого накладные расходы на хранение высоки
- большую часть состояний объектов можно вынести вовне
- многие группы объектов можно заменить относительно небольшим количеством разделяемых объектов, поскольку внешнее состояние вынесено
- приложение не зависит от идернтичности объекта.
Участники
- Flyweight - приспособленец
- объявляет интерфейс, с помощью которого приспособленцы могут получать внешнее состояние или как-то воздействовать на него
- ConcreteFlyweight - конкретный приспособленец
- реализует интерфейс класса Flyweight и добавляет при необходимости внутреннее состояние. Объект класса ConcreteFlyweight должен быть разделяемым. Любое сохраняемое им состояние должно быть внутренним, то есть не зависящим от контекста
- UnsharedConcreteFlyweight - неразделяемый конкретный приспособленец
- не все подклассы Flyweight должны быть разделяемыми. Часто у объектов UnsharedConcreteFlyweight на некотором уровне структуры приспособленца есть потомки в виде объектов класса ConcreteFlyweight.
- FlyweightFactory - фабрика приспособленцев
- создает объекты-приспособленцы и управляет ими
- обеспечивает должное разделение приспособленцев
- Client - клиент
- хранит ссылки на одного или нескольких приспособленцев
- вычисляет или хранит внешнее состояние приспособленцев.
Состояние, необходимое приспособленцу для нормальной работы, можно охарактеризовать как внутреннее или внешнее.
Клиенты не должнны создавать экземпляры класса ConcreteFlyweight напрямую, а могут их получать только от объекта FlyweightFactory. Это позволит гарантировать корректное разделение.
Заместитель (Proxy)
Является суррогатом другого объекта и контролирует доступ к нему.
Паттерн заместитель применим во всех случаях, когда возникает необходимость сослаться на объект более изощренно, чем это возможно, если использовать простой указатель. Типичные ситуации, где полезен заместитель:
-
удаленный заместитель предоставляет локального представителя вместо объекта, находящегося в другом адресном пространстве;
-
виртуальный заместитель создает "тяжелые" объекты по требованию;
-
защищающий заместитель контролирует доступ к исходному объекту. Такие заместители полезны, когда для разных объектов определены различные права доступа;
-
"умная" ссылка - это замена обычного указателя. Она позволяет выполнить дополнительные действия при доступе к объекту.
Участники
-
Proxy - заместитель
- хранит ссылку, которая позволяет заместителю обратиться к реальному субъекту
- предоставляет интерфейс, идентичный интерфейсу Subject, так что заместитель всегда может быть подставлен вместо реального субъекта
- контролирует доступ к реальному субъекту и может отвечать за его создание и удаление
- прочие обязанности зависят от вида заместителя
-
Subject - субъект
- определяет общий для RealSubject и Proxy интерфейс, так что класс Proxy можно использовать везде, где ожидается RealSubject
-
RealSubject - реальный субъект
- определяет реальный объект, представленный заместителем.
Proxy при необходимости переадресует запросы объекту RealSubject. Детали зависят от вида заместителя.
Результаты
С помощью паттерна заместитель при доступе к объекту вводится дополнительный уровень косвенности. У этого подхода есть много вариантов в зависимости от вида заместителя:
- удаленный заместитель может скрыть тот факт, что объект находится в другом адресном пространстве
- виртуальный заместитель может выполнять оптимизацию
- защищающий заместитель и "умная" ссылка позволяют решать дополнительные задачи при доступе к объекту.