Файл: Структурные паттерны 1 Adapter Идея.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 24.10.2023

Просмотров: 20

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.


Структурные паттерны
1)Adapter
Идея: у нас один и тот же объект выполняет несколько ролей, физически это один объект. В разных местах мы работаем с ним по-разному, т.е. разные интерфейсы. Выделить простой класс, а посредники будет представлять нужный̆ класс для работы.
Применение:

  • Хотим использовать существующий класс, но он не отвечает нашим потребностям.

  • Требуется повторно создать класс, который должен взаимодействовать с заранее неизвестными или не связанными с ним классами, имеющие несовместимые интерфейсы.


+”:

  • Отделяет и скрывает от клиента подробности преобразования различных интерфейсов.

  • Позволяет адаптировать интерфейс к требуемому.

  • Позволяет разделять роли и сущности.

  • Можно независимо развивать различные ответственности сущности.

  • Расширение интерфейса класса.


-“:

  • Дублирование кода. В различных адаптерах может требоваться одна и та же реализация методов.

  • Пересечения интерфейсов.


2)Decorator
Идея: нам надо добавить/подменить классам функционал. Причем одинаковый для нескольких классов. Если мы подменим функционал в производных для каждого из классов, которые мы хотим изменить - разрастается иерархия, приходится дублировать код.
Применение:

  • Динамическое, прозрачное для клиентов добавление обязанностей объектам(не затрагивающее другие объекты).

  • Реализация обязанностей, которые могут быть сняты с объекта.

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


+”:

  • Декодирование можно осуществлять в процессе выполнения программы.

  • Отсутствие разрастания иерархии.

  • Избавление от дублирования кода. Код уходит в конкретный декоратор.


-“:

  • Если в цепочке вызовов декоратора необходимо изменить какую-либо обертку или удалить ее, необходимо заново оборачивать исходный объект.

  • За оборачивание и вызов декораторов отвечает программист.

  • Вызов кучи виртуальных методов замедляет работы программы.


3)Composite
Идея: Вынести интерфейс, который предлагает контейнер (объект, включающий в себя другие объекты), на уровень базового класса

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

  • требуется представить иерархию объектов вида «часть — целое»;

  • клиенты должны по единым правилам работать с составными и индивидуальными объектами.


“+”:

  • Упрощает архитектуру клиента при работе со сложным деревом компонентов.

  • Облегчает добавление новых видов компонентов.


“-“:

  • Неудобно осуществить запрет на добавление в составной объект Composite объектов определенных типов. Так, например, в состав римской армии не могут входить боевые слоны.


4)Bridge
Имеются следующие проблемы:

  • У нас большая иерархия. В иерархии возможно несколько внутренних реализаций для объекта, это разные классы, разные ветви. У нас один объект, и во время работы надо поменять реализацию. Каким-то образом нам надо мигрировать от одного класса к другому.

  • Постоянно происходит дублирование кода, когда у нас разрастается иерархия классов.


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

  • Требуется избежать постоянной привязки абстракции к реализации. Так, например, бывает, когда реализация должна выбираться во время выполнения программы;

  • И абстракции, и реализации должны расширяться новыми подклассами. В таком случае паттерн мост позволяет комбинировать разные абстракции и реализации и изменять их независимо;

  • Изменения в реализации абстракции не должны отражаться на клиентах, то есть клиентский̆ код не должен перекомпилироваться;

  • (только для C++) требуется полностью скрыть от клиентов реализацию абстракции. В C++ представление класса видимо через его интерфейс;

  • Реализация должна совместно использоваться несколькими объектами (например, на базе подсчета ссылок), и этот факт должен быть скрыт от клиента. Простой пример — это разработанный̆ Джеймсом Коплиеном класс String [Cop92], в котором разные объекты могут разделять одно и то же представление строки (StringRep).


+”:

  • Убирает дублирование кода.

  • Нет разрастания иерархии.

  • Можем менять реализация во время выполнения программы.


-“:

  • При вызове метода сущности вызывается метод реализации, что увеличивает время работы программы.

  • Усложнение кода программы из-за введения дополнительных классов.


5)Proxy
Идея: Заместитель (или Proxy) позволяет нам работать не с реальным объектом, а с другим объектом, который подменяет реальный.
Применение:

  • Подменяющий объект может контролировать другой объект, задавать правила доступа к этому объекту. Например, у нас есть разные категории пользователей. В зависимости от того, какая у пользователя категория, определять, давать доступ к самому объекту или не давать. Это как защита.

  • Так как запросы проходят через заместителя, он может контролировать запросы, заниматься статистической обработкой: считать количество запросов, какие запросы были и так далее.

  • Разгрузка объекта с точки зрения запросов. Дело в том, что реальные объекты какие-то операции могут выполнять крайне долго, например, обращение "поиск в базе чего-либо" или "обращение по сети куда-то". Это выполняется долго. Proxy может сохранять предыдущий ответ и при следующем обращении смотреть, был ответ на этот запрос или не был. Если ответ на этот вопрос был, он не обращается к самому хозяину, он заменяет его тем ответом, который был ранее. Естественно, если состояние объекта изменилось, Proxy должен сбросить ту историю, которую он накопил, чтобы выдавать только актуальную информацию.


+”:

  • Возможность контролировать какой-либо объект незаметно от клиента.

  • Proxy может работать, когда объекта нет. Например, мы запросили объект, может быть объект был, но ответ для Proxy устарел, этого объекта нет. Proxy может вернуть нам этот ответ, указав, что он устаревший.

  • Proxy может отвечать за жизненный цикл объекта. Он его может создавать и удалять.


-“:

  • Может увеличиться время доступа к объекту (так как идет дополнительная обработка через Proxy).

  • Proxy должен хранить историю обращений, если такая задача стоит. Это влияет на память, она расходуется на хранение запросов и ответов на них.


6)Facade
Идея: У нас есть группа объектов, связанных между собой. Причем эти связи довольно жёсткие. Чтобы извне не работать с каждым объектом в отдельности, мы можем эти все объекты объединить в один класс - фасад, который будет представлять интерфейс для работы со всем этим объединением.
Нам не нужно извне работать с мелкими объектами. Кроме того, фасад может выполнять такую роль, как следить за целостностью нашей̆ системы. Извне, мы, работая с фасадом, работаем, как с простым объектом. Он представляет интерфейс одной сущностью, а внутри у нас целый̆ мир из объектов. Таким образом, упрощается взаимодействие, уменьшается количество связей̆ за счет объединений фасада.
Применение:

  • Представление простого интерфейса к сложной подсистеме.

  • Многочисленные зависимости между клиентами и классами реализации абстракции. Фасад позволяет отделить подсистему как от клиентов, так и от других подсистем, что, в свою очередь, способствует независимости подсистем и повышению уровня переносимости;

  • Требуется разложить подсистему на отдельные уровни. Используйте фасад для определения точки входа на каждый̆ уровень подсистемы. Если подсистемы зависят друг от друга, то зависимости можно упростить, разрешив подсистемам обмениваться информацией̆ только через фасады.

+”:

  • Работа с одним объектом.

  • Доступ к подсистеме обычно делается через 1 метод(run, execute).

  • Оболочка скрывает целостность подсистемы.


-“:

  • Фасад рискует стать объектом привязанным ко всем классам программы.


Порождающие паттерны
1)Factory Method
Идея: Основная идея заключается в том, чтобы избавиться от явного создания объектов. Эти будут заниматься «креаторы». При этом то, какой конкретно объект будет создан, т.е. то, какой креатор использовать будут говорить нам особенная таблица. Иными словами, будет выделяться отдельная сущность, которая будет принимать решение о том, какой объект создавать.

Это дает возможность выбирать объект, который создавать, во время выполнения программы. Так же, во время выполнения программы, мы можем подменять создание одного объекта, на создание другого.
Применение:

  • Подмена одного объекта на другой.

  • Принятие решения в одном месте кода, создание – в другом.


+”:

  • Влияем на создание объекта во время выполнения работы программы.

  • Упрощает добавление новых классов без изменения написанного кода.

  • Помогает разнести в коде принятие решения о создании объекта(solution) и само создание(creator).


-“:

  • Использование полиморфизма, растет время работы программы.



2)Abstract Factory
Идея: создание "семейства" разных объектов, но связанных между собой. Можно "плодить" разные ветви Creаtor'ов под каждый тип продуктов, но мы теряем связь между этими продуктами.
Применение:

  • Система не должна зависеть от того, как создаются, компонуются и представляются входящие в нее объекты.

  • Система должна настраиваться одним из семейств объектов.

  • Входящие в семейство взаимосвязанные объекты спроектированы для совместной работы, и вы должны обеспечить выполнение этого ограничения.


+”:

  • Не надо контролировать создание каждого объекта – только всего семейства целиком.

  • Целостность системы.


-“:

  • Очень сложно все привести к единому интерфейсу.

3)Builder
Идея: Сложные объекты создаются поэтапно, иногда - этапы создания разнесены в разных частях программы. Вынести в отдельный код этапы создания сложных объектов.


  • Строитель - класс, который включает в себя этапы создания сложного объекта.

  • Кроме того, выделяем еще один класс, который контролирует создание - Директор.

  • Строитель - создает объект.

  • Директор - подготавливает данные для создания, контролирует этапы создания, отдает объект клиенту.


Применение:

  • Для поэтапного создания сложных объектов.

  • Когда создание объекта разнесено в коде, объект создается не сразу.


+”:

  • За создание и контроль отвечают разные сущности.

  • Позволяет использовать один и тот же код для создания различных продуктов.

  • Позволяет менять количество этапов создания объекта(введение нового Директора).


-“:

  • Конкретные строители базируются на одних и тех же входных данных, следовательно могут возникнуть проблемы при подмене одного строителя на другой.


4)Signleton
Идея: Во всей системе иметь объект только в единственном экземпляре.
Применение:

  • Должен существовать ровно один экземпляр некоторого класса, к которому может обратиться любой клиент.


+”:

  • Гарантирует наличие единственного экземпляра в системе.

  • Доступ из любой части программы.


-“:

  • Анти-паттерн. Предоставляет собой глобальный объект.

  • Лишаемся подмены. Решение о том, какой объект создавать, принимается на этапе компиляции.


5)Prototype
Идея: Хотим создать копию объекта, не зная его класса. (Например, в подменяемом методе). Также мы не хотим тащить за ним его Creator’ы.
Добавляем в базовый класс метод clone(), который создаст новый объект на основе существующего. Производные классы реализуют clone() под себя.


Применение:

  • Когда создание или уничтожение какого-либо объекта - трудоемкий процесс и надо "держать" определенное количество объектов в системе.


+”:

  • Позволяет клонировать объекты, не привязываясь к их конкретным классам.

  • Ускоряет создание объектов.


-“:

  • Сложно клонировать составные объекты, имеющие ссылки на другие объекты, а также если во внутреннем представлении объекта присутствуют другие объекты.


6)Object Pool
Идея: Если нам необходим, возможно ограниченный, набор определенных объектов. При запросе объекта мы даем его из этого набора, после использования возвращаем назад, при этом вернув в исходное состояние.
Применение:

  • Создание или уничтожение какого-либо объекта - трудоёмкий процесс и надо "держать" определенное количество объектов в системе.


+”:

  • Улучшает производительность.


-“:

  • После использования возвращаем его в пул. Здесь возможно утечка информации. Его надо вернуть в исходное состояние или очистить.


Паттерны поведения
1)Strategy
Идея: То, что может меняться (это действие) вынести в отдельный класс, который будет выполнять только это действие. Мы можем во время выполнения подменять одно на другое.
Применение:

  • Наличие нескольких разновидностей алгоритма.

  • Чтобы скрыть сложные для алгоритма структуры данных.


+”:

  • Возможность подмены алгоритма.

  • Отделение сущности от реализации.

  • Ограничения разрастания иерархии.

  • Уменьшение дублирования кода.

  • Добавление функционала классу.

-”:

  • Не всегда можем свести к базовой, реализация может быть разная, а мы хотим подменять, алгоритм базируется на обработке данных, а данные могут быть разные.


2)Command
Идея: Возможны разные запросы (загрузить, повернуть, перенести и подобное). Можно обернуть каждый запрос в отдельный класс (класс может быть как простой, так и составной). Если мы чётко знаем, кто должен обработать запрос, то надо держать объект (обработчик) и указатель на метод, и их можно передавать тому, кто это обработает.
Применение:

  • Структурирование системы.


+”:

  • Единообразие обработки запросов к системе.

  • Уменьшается зависимость между объектами, не нужно держать связь.

  • Можно сформировать очередь.

  • Формирование сложный команд из простых. (если добавить Composite)


-”:
3)Chain of responsibility
Идея: создать цепочку обработчиков.
Применение:

  • Один и тот же запрос может выполняться разными способами.

  • Есть четка последовательность в обработчиках(модем передавать до тех пор, пока не обработается).


+”:

  • Позволяет передавать запрос последовательно по цепочке обработчиков, каждый обработчик сам решает, передавать дальше или нет.


-”:
4)Publish Subscribe
Идея: Часто нам надо какой-либо запрос передавать не одному, а многим объектам, и это должно выполняться на этапе выполнения.

Группа объектов реагирует на один объект, на его изменения, издатель оповещает всех подписчиков, когда происходят изменения, вызывая их методы.
Применение:
+”:

  • Издатель не зависит от подписчиков.

  • Гибка схема: модно как отписаться, так и подписаться.


-”:

  • Издатель должен держать список объектов, которые на него подписаны.

  • Нет порядка в оповещении подписчиков.

  • Если издателей много(каждый подписчик может быть издателем), то будет много связей.


5)Mediator
Идея: (последний недостаток подписчика-издателя*(издателей много - много связей)*) связи вынести в отдельный объект, тогда каждый объект будет обращаться к этому отдельному объекту, а он в свою очередь будет искать нужные связи (с кем ему связаться).
Применение:

  •  Уменьшить количество связей между объектами благодаря перемещению связей в него, в посредник. Так как посредник должен работать со всеми объектами, то он содержит указатели на все эти объекты.


+”:

  • Упрощает объекты(выносим из них связи).

  • Нет прямой зависимости между ними.

  • Происходит централизованное взаимодействие(появляется контроль).


-”:

  • Использование посредников замедляет работу программы.


6)Visitor
Идея: Следующая проблема - связанная с изменением интерфейса объектов. Если мы используем полиморфизм, мы не можем в производном кассе ни сузить, ни расширить интерфейс, так как он должен четко поддерживать интерфейс базового класса.
Если нам необходимо расширить интерфейс, можно использовать паттерн Визитёр. Он позволяет во время выполнения (в отличие от паттерна Адаптера, который решает эту проблему до выполнения) подменить или расширить функционал.
Применение:

  • Над объектами, входящими в состав структуры, должны выполняться разнообразные, не связанные между собой̆ операции и вы не хотите «засорять» классы такими операциями.


+”:

  • Объединение разных иерархий в одну.

  • Значительное упрощение схемы.

  • Отсутствие оберточных функций.


-“:

  • Расширяется иерархия, добавляются новые классы. Проблема связи на уровне базовых классов.

  • Может меняться иерархия. Тогда посетитель не срабатывает.

  • Необходимо знать реализацию объектов – установление дружественных связей.


7)Memento
Идея: Когда мы выполняем много операций, объект изменяется, но, те изменения которые у нас произошли, могут нас не устраивать, и мы можем вернуться к предыдущему состоянию нашего объекта (откат). Можно выделить эту обязанность другому объекту, задача которого - хранить предыдущие состояния нашего объекта, который, если нужно, позволит нам вернуться к какому-либо предыдущему состоянию.
Применение:
+”:

  • Позволяет не грузить сам класс задачей сохранять предыдущее состояние.

  • Предоставляет возможность откатываться на несколько состояние назад.


-“:

  • Опекуном надо управлять. Он наделал снимков, а они нам не нужны. Кто-то должен очищать. Много тратится памяти.


8)Template Method
Идея: Этот паттерн является скелетом какого-либо метода. Мы любую задачу разбиваем на этапы - формируем шаги, которые мы выполняем для того, чтобы то, что мы получили на входе, преобразовать в результат, который нам нужен.
Применение:
+”:

  • Даёт возможность предоставления собственной̆ реализации метода

  • Позволяет избежать дублирования кода в субклассах, т.к. общая логика алгоритма вынесена в абстрактный̆ класс

  • Упрощает расширение функциональности, т.к. добавлене новых шагов алгоритма

или изменение порядка выполнения шагов может быть реализовано в субклассах без изменения общей структуры алгоритма.
-“:

  • Может привести к созданию большого количества субклассов, если алгоритм имеет много шагов и каждый̆ шаг должен быть изменен в отдельном субклассе.



  • Может усложнить понимание кода


9)Holder
Существует проблема. Предположим, у нас есть класс А, в котором есть метод f(). Мы не знаем, что творится внутри f(), и, естественно, мы используем механизм обработки исключительных ситуаций. Внутри f() происходит исключительная ситуация, она приводит к тому, что мы перескакиваем на какой̆-то обработчик, неизвестно где находящийся. Это приводит к тому, что объект p не удаляется - происходит утечка памяти.
Идея: обернуть объект в оболочку, которая статическая распределяет память. Эта оболочка будет отвечать за этот указатель. И соответственно, поскольку мы статически распределили, когда будет вызываться деструктор, в деструкторе мы будем освобождать память.
Применение:
+”:

  • Помогает избежать утечек памяти.


-“:

  • Опасность наличия операции взятия адреса (есть риск возникновения висячих указателей).


10)Iterator
Идея: выделить текущий указатель в структуру и создавать по надобности:
Применение:
+”:

  • Упрощает работу с коллекциями, позволяя обходить их элементы без знания о внутренней структуре коллекции.

  • Позволяет работать с различными типами коллекций независимо от их реализации

  • Позволяет реализовывать различные алгоритмы обхода коллекций


-“:

  • Добавление новых типов коллекций может потребовать изменения кода итератора

  • Итератор может стать ненадежным, если коллекция изменяется во время обхода.


11)Property
Идея: Формализация свойств.
Применение:
+”:

  • Реализация принципа инкапсуляции

  • Обеспечивает контроль доступа к данным, что позволяет избежать ошибок при

работе с объектами.

  • Позволяет добавлять дополнительную логику при доступе к данным, например, проверку на корректность значений или логирование.


-“:

  • Может привести к увеличению количества кода, так как для каждого поля объекта требуется создание методов доступа.

  • Может усложнить понимание кода, т.к. доступ к данным может быть скрыт в методах

class Facade

{

public:

Facade();

Facade() = default;

void execute(BaseCommand &command);

private:

std::shared_ptr _drawManager;

std::shared_ptr _loadManager;

std::shared_ptr _sceneManager;

std::shared_ptr _transformManager;

std::shared_ptr _modelLoadModerator;

std::shared_ptr _sceneLoadModerator;

std::shared_ptr _drawCompositeAdapter;

};

Array::Array(const Array& ar) : count(ar.count)

{

arr = new double[count];

for (int i = 0; i < count; ++i)

arr[i] = ar.arr[i];

}
Array::Array(Array&& ar) noexcept : count(ar.count)

{

arr = ar.arr;

ar.arr = nullptr;

}

int main()

{

int i;

int& a = i;

const int& b = 0;
decltype(auto) r1 = f(i);

// 1. T f(T v)--->int f(int)

// 2. T f(T& v)--->int f(int&)

// 3. T f(const T& v)--->int f(const int&)

// 4. T f(T&& v)--->int& f(int&)

// 5. T& f(T&& v)--->int& f(int&)

// 6. T&& f(T&& v) { return std::forward(v); }--->int& f(int&)

// 7. auto f(auto v)--->int f(int)

// 8. auto f(auto& v)--->int f(int&)

// 9. auto f(const auto& v)--->int f(const int&)

// 10. auto&& f(auto&& v)--->int& f(int&)

// 11. auto&& f(auto&& v) { return std::forward(v); }

// --->int& f(int&)

// 12. decltype(auto) f(auto&& v) { return std::forward(v); }

// --->int& f(int&)

// 13. auto f(T&& v) -> decltype(v) { return std::forward(v); }

//--->int& f(int&)
decltype(auto) r2 = f(a);

// 1. T f(T v)--->int f(int)

// 2. T f(T& v)--->int f(int&)

// 3. T f(const T& v)--->int f(const int&)

// 4. T f(T&& v)--->int& f(int&)

// 5. T& f(T&& v)--->int& f(int&)

// 6. T&& f(T&& v) { return std::forward(v); }--->int& f(int&)

// 7. auto f(auto v)--->int f(int)

// 8. auto f(auto& v)--->int f(int&)

// 9. auto f(const auto& v)--->int f(const int&)

// 10. auto&& f(auto&& v)--->int& f(int&)

// 11. auto&& f(auto&& v) { return std::forward(v); }

// --->int& f(int&)

// 12. decltype(auto) f(auto&& v) { return std::forward(v); }

// --->int& f(int&)

// 13. auto f(T&& v) -> decltype(v) { return std::forward(v); }

//--->int& f(int&)
decltype(auto) r3 = f(b);

// 1. T f(T v)--->int f(int)

// 2. T f(T& v)--->const int f(const int&)

// 3. T f(const T& v)--->int f(const int&)

// 4. T f(T&& v)--->const int& f(const int&)

// 5. T& f(T&& v)--->const int& f(const int&)

// 6. T&& f(T&& v) { return std::forward(v); }

// --->const int& f(const int&)

// 7. auto f(auto v)--->int f(int)

// 8. auto f(auto& v)--->int f(const int&)

// 9. auto f(const auto& v)--->int f(const int&)

// 10. auto&& f(auto&& v)--->const int& f(const int&)

// 11. auto&& f(auto&& v) { return std::forward(v); }

// --->const int& f(const int&)

// 12. decltype(auto) f(auto&& v) { return std::forward(v); }

// --->const int& f(const int&)

// 13. auto f(T&& v) -> decltype(v) { return std::forward(v); }

//--->const int& f(const int&)
decltype(auto) r4 = f(std::move(a));

// 1. T f(T v)--->int f(int)

// 2. T f(T& v)--->Error!

// 3. T f(const T& v)--->int f(const int&)

// 4. T f(T&& v)--->int f(int)

// 5. T& f(T&& v)--->int& f(int&&)

// 6. T&& f(T&& v) { return std::forward(v); }--->int&& f(int&&)

// 7. auto f(auto v)--->int f(int)

// 8. auto f(auto& v)--->Error!

// 9. auto f(const auto& v)--->int f(const int&)

// 10. auto&& f(auto&& v)--->int f(int&&)

// 11. auto&& f(auto&& v) { return std::forward(v); }

// --->int&& f(int&&)

// 12. decltype(auto) f(auto&& v) { return std::forward(v); }

// --->int&& f(int&&)

// 13. auto f(T&& v) -> decltype(v) { return std::forward(v); }

//--->int&& f(int&&)

}