Skip to content

О паттернах, которые я использовал в 3 лабораторной работе

Inspirate789 edited this page May 12, 2022 · 9 revisions

Эпиграф

“Мы должны модифицировать нашу программу, не изменяя уже написанный код.”
Тассов К. Л.
"Вы должны глубоко продумывать и прорабатывать архитектуру. Вы должны рассматривать систему в развитии, предполагая, что она будет постоянно модифицироваться. И любая модификация [в каком месте системы она бы не возникла] не должна приводить к изменению уже написанного кода."
Тассов К. Л.

Именно поэтому мы создаём кучу классов.

"В процессе разработки нужно постоянно задавать себе вопрос: "А если я здесь что-то изменю, что будет?" Сейчас это будет идти тяжело, а потом будет на автоматизме. Вы все эти моменты будете чувствовать уже ... другими местами [ахахах, тонко]. ... Через некоторое время вы будете это воспринимать как структурное программирование."
Тассов К. Л.

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

Цель лабораторной работы

Продумать и реализовать такую архитектуру, чтобы её можно было легко модифицировать, не изменяя (вообще никак) уже написанный код. Мы должны продумывать структуру так, чтобы мы могли в любое место программы впихнуть новый класс и начать его использовать, вообще не меняя то, что мы уже написали. Классно же, да?

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

Итак, перейдём к паттернам, которые я использовал в 3 лабе по ООП:

Порождающие паттерны

Factory Method (фабричный метод)

Решаемая проблема

Необходимо избавиться от операторов new, раскиданных по всей программе, обернув их, и унифицировать создание объектов конкретного класса (сделать создание только в одном месте).

Когда надо применять паттерн

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

Для того, чтобы система оставалась независимой от различных типов объектов, паттерн Factory Method использует механизм полиморфизма - классы всех конечных типов наследуют от одного абстрактного базового класса, предназначенного для полиморфного использования. В этом базовом классе определяется единый интерфейс, через который пользователь будет оперировать объектами конечных типов.

Для обеспечения относительно простого добавления в систему новых типов паттерн Factory Method локализует создание объектов конкретных типов в специальном классе-фабрике. Методы этого класса, посредством которых создаются объекты конкретных классов, называются фабричными.

Описание

image

Участники

  • Абстрактный класс Product определяет интерфейс класса, объекты которого надо создавать.
  • Конкретные классы ConcreteProductA и ConcreteProductB представляют реализацию класса Product. Таких классов может быть множество
  • Абстрактный класс Creator определяет абстрактный фабричный метод FactoryMethod(), который возвращает объект Product.
  • Конкретные классы ConcreteCreatorA и ConcreteCreatorB - наследники класса Creator, определяющие свою реализацию метода FactoryMethod(). Причем метод FactoryMethod() каждого отдельного класса-создателя возвращает определенный конкретный тип продукта. Для каждого конкретного класса продукта определяется свой конкретный класс создателя.

Таким образом, класс Creator делегирует создание объекта Product своим наследникам. А классы ConcreteCreatorA и ConcreteCreatorB могут самостоятельно выбирать какой конкретный тип продукта им создавать.

Преимущества

  • Код очищается от new, используя механизм полиморфизма по полной.
  • Создаём новые классы, не изменяя уже написанный код.
  • Этот паттерн работает во всех языках программирования
  • Позволяет разнести в коде принятие решения о создании объекта и само создание ==> эти задачи можно делегировать разным классам
  • Решение о том, какой объект создать, принимается во время выполнения программы (тогда же можем менять решение в зависимости от каких-либо данных)

Пример из реальной жизни

Паттерн “Фабричный метод” довольно сложно объяснить в метафорах, но всё же попробую.

Ключевой сложностью объяснения данного паттерна является то, что это «метод», поэтому метафора метода будет использовано как действие, то есть, например, слово «Хочу!». Соответственно, паттерн описывает то, как должно выполнятся это «Хочу!».

Допустим ваша фабрика производит пакеты с разными соками. Теоретически мы можем на каждый вид сока делать свою производственную линию, но это не эффективно. Удобнее сделать одну линию по производству пакетов-основ, а разделение ввести только на этапе заливки сока, который мы можем определять просто по названию сока. Однако откуда взять название?

Для этого мы создаем основной отдел по производству пакетов-основ и предупреждаем все подотделы, что они должны производить нужный пакет с соком про простому «Хочу!» (т.е. каждый подотдел должен реализовать паттерн «фабричный метод»). Поэтому каждый подотдел заведует только своим типом сока и реагирует на слово «Хочу!».

Таким образом если нам потребуется пакет апельсинового сока, то мы просто скажем отделу по производству апельсинового сока «Хочу!», а он в свою очередь скажет основному отделу по созданию пакетов сока, «Сделай-ка свой обычный пакет и вот сок, который туда нужно залить».

Abstract Factory (абстрактная фабрика)

Решаемая проблема

Возникает необходимость создания похожих (но не родственных) объектов, связанных одной библиотекой или приложением. И при этом мы хотим иметь возможность подменять библиотеки. А Creator’ы из паттерна Factory Method отвечают только за объекты одной иерархии.

Паттерн "Абстрактная фабрика" (Abstract Factory) предоставляет интерфейс для создания семейств взаимосвязанных объектов с определенными интерфейсами без указания конкретных типов данных объектов.

Когда надо применять паттерн

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

Описание

image

Участники

  • Абстрактные классы AbstractProductA и AbstractProductB определяют интерфейс для классов, объекты которых будут создаваться в программе.
  • Конкретные классы ProductA1 / ProductA2 и ProductB1 / ProductB2 представляют конкретную реализацию абстрактных классов
  • Абстрактный класс фабрики AbstractFactory определяет методы для создания объектов. Причем методы возвращают абстрактные продукты, а не их конкретные реализации.
  • Конкретные классы фабрик ConcreteFactory1 и ConcreteFactory2 реализуют абстрактные методы базового класса и непосредственно определяют какие конкретные продукты использовать
  • Класс клиента Client использует класс фабрики для создания объектов. При этом он использует исключительно абстрактный класс фабрики AbstractFactory и абстрактные классы продуктов AbstractProductA и AbstractProductB и никак не зависит от их конкретных реализаций

Недостаток

  • Интерфейс AbstractFactory сложно сделать универсальным (под разные иерархии).

Пример из реальной жизни

Суть паттерна практически полностью описывается его названием. Когда вам требуется получать какие-то объекты, например, пакеты сока, вам совершенно не нужно знать, как их делают на фабрике. Вы просто говорите «сделайте мне пакет апельсинового сока», а «фабрика» возвращает вам требуемый пакет. Как? Всё это решает сама фабрика, например, «копирует» уже существующий эталон. Основное предназначение «фабрики» в том, чтобы можно было при необходимости изменять процесс «появления» пакета сока, а самому потребителю ничего об этом не нужно было сообщать, чтобы он запрашивал его, как и прежде.

Builder (строитель)

Решаемая проблема

Если наш объект сложный, то, возможно, его нужно будет создавать по частям (как в 1 лабе мы последовательно собирали наш объект “Фигура” из объектов “Точки” и “Связи”, поэтапно считывая их из текстового файла). Тогда можно в один отдельный класс свести все этапы создания какого-то объекта.
А кто будет выполнять все эти этапы? В каком порядке?
А кто будет готовить данные для этих этапов?
А кто будет контролировать процесс создания объектов?
Директор.

Описание

image

Участники

  • Product: представляет объект, который должен быть создан. В данном случае все части объекта заключены в списке parts.
  • Builder: определяет интерфейс для создания различных частей объекта Product
  • ConcreteBuilder: конкретная реализация Builder'a. Создает объект Product и определяет интерфейс для доступа к нему
  • Director: распорядитель - создает объект, используя объекты Builder

Мы унифицируем процесс создания объектов за счёт директора, которого мы (клиент) просим создать объект и вернуть его. А как это будет происходить, волнует уже директора.

"Разделяй и властвуй. И думай всегда о главном."
Тассов К. Л.

Пример из реальной жизни

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

Структурные паттерны

Adapter (адаптер)

Решаемая проблема

Необходимо сделать возможным взаимодействие классов, интерфейсы которых несовместимы.

Часто в новом программном проекте не удается повторно использовать уже существующий код. Например, имеющиеся классы могут обладать нужной функциональностью, но иметь при этом несовместимые интерфейсы. В таких случаях следует использовать паттерн Adapter (адаптер).

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

Рассмотрим простой пример, когда следует применять паттерн Adapter. Пусть мы разрабатываем систему климат-контроля, предназначенной для автоматического поддержания температуры окружающего пространства в заданных пределах. Важным компонентом такой системы является температурный датчик, с помощью которого измеряют температуру окружающей среды для последующего анализа. Для этого датчика уже имеется готовое программное обеспечение от сторонних разработчиков, представляющее собой некоторый класс с соответствующим интерфейсом. Однако использовать этот класс непосредственно не удастся, так как показания датчика снимаются в градусах Фаренгейта. Нужен адаптер, преобразующий температуру в шкалу Цельсия.

Контейнеры queue, priority_queue и stack библиотеки стандартных шаблонов STL реализованы на базе последовательных контейнеров list, deque и vector, адаптируя их интерфейсы к нужному виду. Именно поэтому эти контейнеры называют контейнерами-адаптерами.

Описание

Пусть класс, интерфейс которого нужно адаптировать к нужному виду, имеет имя Adaptee. Для решения задачи преобразования его интерфейса паттерн Adapter вводит следующую иерархию классов:

  • Виртуальный базовый класс Target. Здесь объявляется пользовательский интерфейс подходящего вида. Только этот интерфейс доступен для пользователя.
  • Производный класс Adapter, реализующий интерфейс Target. В этом классе также имеется указатель или ссылка на экземпляр Adaptee. Паттерн Adapter использует этот указатель для перенаправления клиентских вызовов в Adaptee. Так как интерфейсы Adaptee и Target несовместимы между собой, то эти вызовы обычно требуют преобразования.

image

Участники

  • Target: представляет объекты, которые используются клиентом
  • Client: использует объекты Target для реализации своих задач
  • Adaptee: представляет адаптируемый класс, который мы хотели бы использовать у клиента вместо объектов Target
  • Adapter: собственно адаптер, который позволяет работать с объектами Adaptee как с объектами Target. То есть клиент ничего не знает об Adaptee, он знает и использует только объекты Target. И благодаря адаптеру мы можем на клиенте использовать объекты Adaptee как Target

Преимущества

  • Адаптер позволяет разделить роли (интерфейсы) одного объекта
  • Решается проблема подмены интерфейса (например, можно подменить библиотеку, с которой мы взаимодействуем через адаптер)
  • Можно использовать адаптер для расширения интерфейса класса без использования расширения при наследовании (использовать только в крайних случаях, т. к. надо приводить тип от базового класса к производному, используя dynamic_cast)
  • Можно использовать старый код (из других программ), адаптируя его нашим новым интерфейсам

Недостаток:

  • Интерфейсы, которые предоставляют адаптеры, могут пересекаться ==> может возникнуть дублирование кода

Пример из реальной жизни

Данный паттерн полностью соответствует своему названию. Чтобы заставить работать «советскую» вилку через евро-розетку требуется переходник. Именно это и делает «адаптер», служит промежуточным объектом между двумя другими, которые не могут работать напрямую друг с другом.

Facade (фасад)

Решаемая проблема

Клиенты хотят получить упрощенный интерфейс к общей функциональности сложной подсистемы.

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

Паттерн Facade инкапсулирует сложную подсистему в единственный интерфейсный объект. Это позволяет сократить время изучения подсистемы, а также способствует уменьшению степени связанности между подсистемой и потенциально большим количеством клиентов. С другой стороны, если фасад является единственной точкой доступа к подсистеме, то он будет ограничивать возможности, которые могут понадобиться "продвинутым" пользователям.

Объект Facade, реализующий функции посредника, должен оставаться довольно простым и не быть всезнающим "оракулом".

Описание

Клиенты общаются с подсистемой через Facade. При получении запроса от клиента объект Facade переадресует его нужному компоненту подсистемы. Для клиентов компоненты подсистемы остаются "тайной, покрытой мраком".

image

Другая диаграмма:

image

Участники

  • Классы SubsystemA, SubsystemB, SubsystemC и т.д. являются компонентами сложной подсистемы, с которыми должен взаимодействовать клиент
  • Client взаимодействует с компонентами подсистемы
  • Facade - непосредственно фасад, который предоставляет интерфейс клиенту для работы с компонентами

Пример из реальной жизни

Паттерн «фасад» используется для того, чтобы делать сложные вещи простыми. Возьмем для примера автомобиль. Представьте, если бы управление автомобилем происходило немного по-другому: нажать одну кнопку чтобы подать питание с аккумулятора, другую чтобы подать питание на инжектор, третью чтобы включить генератор, четвертую чтобы зажечь ламочку на панели и так далее. Всё это было бы очень сложно. Для этого такие сложные наборы действий заменяются более простыми и комплексные как «повернуть ключ зажигания». В данном случае поворот ключа зажигания и будет тем самым «фасадом» для всего обилия внутренних действий автомобиля.

А ещё тут, по-моему, очень напрашивается аналогия с красивым фасадом дома...

Composite (компоновщик)

Решаемая проблема

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

Используйте паттерн Composite, если

  • Необходимо объединять группы схожих объектов и управлять ими.
  • Объекты могут быть как примитивными (элементарными), так и составными (сложными). Составной объект может включать в себя коллекции других объектов, образуя сложные древовидные структуры. Пример: директория файловой системы состоит из элементов, каждый из которых также может быть директорией.
  • Код клиента работает с примитивными и составными объектами единообразно.

Описание

Как управлять группами объектов, которые, в свою очередь, могут содержать собственные объекты?

Паттерн Composite предлагает следующее решение. Он вводит абстрактный базовый класс Component с поведением (метод operation()), общим для всех примитивных и составных объектов. Подклассы Primitive и Composite являются производными от класса Component. Составной объект Composite хранит компоненты-потомки абстрактного типа Component, каждый из которых может быть также Composite.

image

Участники

  • Component: определяет интерфейс для всех компонентов в древовидной структуре
  • Composite: представляет компонент, который может содержать другие компоненты и реализует механизм для их добавления и удаления
  • Primitive: представляет отдельный компонент, который не может содержать другие компоненты
  • Client: клиент, который использует компоненты

Пример из реальной жизни

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

Паттерны поведения

Command (команда)

Решаемая проблема

Унификация обработки событий (запросов) в какой-либо системе.

Используйте паттерн Command, если

  • Система управляется событиями. При появлении такого события (запроса) необходимо выполнить определенную последовательность действий.
  • Необходимо параметризировать объекты выполняемым действием, ставить запросы в очередь или поддерживать операции отмены (undo) и повтора (redo) действий.
  • Нужен объектно-ориентированный аналог функции обратного вызова в процедурном программировании.

Пример событийно-управляемой системы – приложение с пользовательским интерфейсом. При выборе некоторого пункта меню пользователем вырабатывается запрос на выполнение определенного действия (например, открытия файла).

Паттерн Command преобразовывает запрос на выполнение действия в отдельный объект-команду. Такая инкапсуляция позволяет передавать эти действия другим функциям и объектам в качестве параметра, приказывая им выполнить запрошенную операцию. Команда – это объект, поэтому над ней допустимы любые операции, что и над объектом.

Описание

Интерфейс командного объекта определяется абстрактным базовым классом Command и в самом простом случае имеет единственный метод execute(). Производные классы определяют получателя запроса (указатель на объект-получатель) и необходимую для выполнения операцию (метод этого объекта). Метод execute() подклассов Command просто вызывает нужную операцию получателя.

В паттерне Command может быть до трех участников:

  • Клиент, создающий экземпляр командного объекта.
  • Инициатор запроса, использующий командный объект.
  • Получатель запроса.

Сначала клиент создает объект ConcreteCommand, конфигурируя его получателем запроса. Этот объект также доступен инициатору. Инициатор использует его при отправке запроса, вызывая метод execute(). Этот алгоритм напоминает работу функции обратного вызова в процедурном программировании – функция регистрируется, чтобы быть вызванной позднее.

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

image

Участники

  • Invoker: инициатор команды - вызывает команду для выполнения определенного запроса
  • Receiver: получатель команды. Определяет действия, которые должны выполняться в результате запроса.
  • Command: интерфейс, представляющий команду. Обычно определяет метод Execute() для выполнения действия, а также нередко включает метод Undo(), реализация которого должна заключаться в отмене действия команды
  • ConcreteCommand: конкретная реализация команды, реализует метод Execute(), в котором вызывается определенный метод, определенный в классе Receiver

Пример из реальной жизни

Паттерн «команда» очень похож в реальной жизни на кнопки выключателей света в наших квартирах и домах. Каждый выключатель по своей сути делает одно простое действие — разъединяет или соединяет два провода, однако что стоит за этими проводами выключателю не известно. Что подключат, то и произойдет. Точно также действует и паттерн «команда». Он лишь определяет общие правила для объектов (устройств), в виде соединения двух проводов для выполнения команды, а что именно будет выполнено уже определяет само устройство (объект).

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

Visitor (посетитель)

Решаемая проблема

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

Когда надо применять паттерн

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

Описание

image

Участники

  • Visitor: интерфейс посетителя, который определяет метод Visit() для каждого объекта Element
  • ConcreteVisitor1 / ConcreteVisitor2: конкретные классы посетителей, реализуют интерфейс, определенный в Visitor.
  • Element: определяет метод Accept(), в котором в качестве параметра принимается объект Visitor
  • ElementA / ElementB: конкретные элементы, которые реализуют метод Accept()
  • ObjectStructure: некоторая структура, которая хранит объекты Element и предоставляет к ним доступ. Это могут быть и простые списки, и сложные составные структуры в виде деревьев

Реализация:

Сущность работы паттерна состоит в том, что вначале создает объект посетителя, который обходит или посещает все элементы в структуре ObjectStructure, у которой вызывается метод Accept():
(да-да, я своровал примеры на C#, это неважно!!!!!)

public void Accept(Visitor visitor)
{
    foreach (Element element in elements)
                element.Accept(visitor);
}

При посещении каждого элемента посещаемый элемент вызывает у посетителя метод, соответствующий данному элементу:

public override void Accept(Visitor visitor)
{
    visitor.VisitElementA(this);
}

В этот метод элемент передает ссылку на себя, чтобы посетитель мог получить доступ к состоянию элемента. А в самом посетителе уже могут вызываться методы элемента или производиться различные действия над элементом:

public override void VisitElementA(ElementA elementA)
{
    elementA.OperationA();
}

Данная техника еще называется двойной диспетчеризацией (double dispatch), когда выполнение операции зависит от имени запроса и двух типов получателей (объект Visitor и объект Element).

Особенности паттерна Visitor:

  • Совокупная структура объектов Elements может определяться с помощью паттерна Composite (но не обязательно должна!)
  • Для обхода Composite может использоваться Iterator.
  • Паттерн Visitor демонстрирует классический прием восстановления информации о потерянных типах, не прибегая к понижающему приведению типов (dynamic cast).

Пример из реальной жизни

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

Для чего я использую каждый паттерн в 3 лабе

  • Factory method (фабричный метод): для единообразной генерации менеджеров и предотвращения повторного создания сущностей (“Creator - шикарная альтернатива синглтону”). Классы: иерархия ...ManagerCreator (маскируют синглтоны), иерархия ...ModeratorCreator.

  • Abstract factory (абстрактная фабрика): фабрика рисования (обёртка над библиотекой). Классы: Drawerfactory, QtFactory, BaseDrawer, QtDrawer.

  • Builder (строитель): для генерации каркасной модели: сначала строим точки, потом рёбра. Классы: Директоры (классы иерархии BaseDirector) руководят строителями (классами иерархии BaseBuilder).

В Порождающих паттернах должны быть сущности (Solution в случае фабричного метода или абстрактной фабрики или Director в случае строителя), которые принимают решения, какой Creator создать или как работать с Builder.

  • Adapter (адаптер): для расширения интерфейса (легального, Тассов не побьёт) модели. Конкретнее: мы адаптируем модель (и композит) к DrawManager и TransformManager, чтобы они могли с ней работать. Классы: иерархии CompositeAdapter и ModelAdapter.

  • Facade (фасад) - для упрощения более сложного интерфейса. Класс Facade объединяет классы SceneManager, TransformManager, LoadManager и DrawManager.

  • Composite (компоновщик) - для объединения нескольких объектов в группы. Классы: Composite, VisibleObject, InvisibleObject и их производные.

  • Command (команда) - для единообразного взаимодействия с системой. Классы: иерархия BaseCommand.

  • Visitor (посетитель) - для добавления нового функционала объектам, а также для отрисовки и трансформации. Классы: Visitor, DrawVisitor “посещает” структуру Composite (т.е. всё, что завёрнуто в паттерн Composite).

Паттерн Visitor есть только в старых версиях моей диаграммы.

Тассов сказал, что если мы добавим в композит новый объект (например, источник света вдобавок к модели и камере), то визитёра придётся менять, добавляя метод visit() для источника света. А мы не должны менять уже написанный код! Было предложено 2 выхода:

  1. Сделать визитора с переменным числом параметров (аналогичный есть в STL, но мы должны сделать его своими руками)
  2. Наплодить кучу адаптеров. Именно наплодить, потому что вместо 3 классов в случае визитора (Visitor, DrawVisitor и TransformVisitor) мы делаем 6 (см. диаграмму).

Лично мне больше понравился 2 вариант. Это чисто моё субъективное мнение. Тассов сказал, что в данном случае адаптеры дают нам очень большую гибкость.

Clone this wiki locally