Обзор шаблона проектирования Visitor и его реализации в Dart и Flutter

В прошлой статье я проанализировал шаблон поведенческого проектирования, который обеспечивает слабую связь между отправителем запроса и его получателем - Chain of Responsibility. В этой статье я хотел бы проанализировать и реализовать еще один шаблон поведенческого проектирования, который позволяет отделить алгоритмы от объектов, с которыми они работают, - это Visitor.

Оглавление

  • Что такое шаблон проектирования "Посетитель"?
  • Анализ
  • Реализация
  • Другие статьи из этой серии
  • Ваш вклад

Что такое шаблон проектирования "Посетитель"?

Посетитель относится к категории шаблонов поведенческого проектирования. Назначение этого паттерна проектирования описано в Книге GoF:

Представляют собой операцию, выполняемую над элементами структуры объекта. Visitor позволяет вам определить новую операцию, не изменяя классы элементов, с которыми он работает.

Допустим, у нас есть сложная объектная структура, возможно, это дерево или коллекция, состоящая из нескольких различных компонентов класса. Теперь мы хотим добавить к этим компонентам какую-то новую функциональность, не меняя сами классы - возможно ли это?

Ключевой идеей здесь является определение операции двойной отправки (в контексте шаблона проектирования Visitor операция называется accept) для каждого конкретного сложного класса объектов - я знаю, вы могли подумать, что я солгал вы о добавлении новых операций без изменения существующего кода, но подождите, для этого есть веская причина! Когда клиенты пересекают структуру объекта, для каждого элемента вызывается метод accept, чтобы делегировать запрос конкретному объекту посетитель, который передается методу в качестве параметра. Затем вызывается конкретный метод объекта посетителя (запрос делегируется), следовательно, выполняется фактический запрос. Это основная идея операции двойной отправки - клиент отправляет запрос компоненту, а компонент делегирует запрос методу конкретного посетителя. Это означает, что достаточно реализовать один метод для класса компонента, и тогда вы можете определить новую операцию над структурой объекта, просто добавив нового посетителя. И на этот раз вы можете реализовать столько различных классов посетителей, сколько захотите, без изменения существующего кода! Как это круто?

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

Сначала все условия посетитель, accept, двойная отправка могут выглядеть запутанными - не волнуйтесь, это становится намного понятнее, когда вы видите шаблон дизайна посетителя в действии. Давайте перейдем к частям анализа и реализации, чтобы понять и узнать подробности об этом шаблоне и о том, как его реализовать!

Анализ

Общая структура паттерна дизайна Visitor выглядит так:

  • Посетитель - объявляет операцию посещения для каждого класса конкретного элемента в структуре объекта. Если язык программирования поддерживает перегрузку функций, операции посещения могут иметь одно и то же имя (Dart на данный момент не поддерживает это), но тип их параметров должен быть другим. Обычно имя и подпись операции отличаются и определяют класс (конкретный элемент), который отправляет посетителю запрос на посещение;
  • Конкретные посетители - реализует каждую операцию, объявленную Посетителем;
  • Element - объявляет метод accept, который принимает Visitor в качестве аргумента;
  • Бетонные элементы - реализует метод приема. Реализация должна полагаться на перенаправление запроса подходящему методу посетителя, соответствующему текущему классу элемента;
  • Клиент - обычно содержит коллекцию или сложную структуру объекта, инициализирует объект конкретного посетителя, а затем просматривает структуру объекта, посещая каждый элемент вместе с посетителем.

Применимость

Основная цель шаблона проектирования Visitor - отделить алгоритмы от объектов, с которыми они работают, и, следовательно, очистить бизнес-логику. Таким образом, классы вашего приложения могут сосредоточиться на своей основной работе, в то время как вспомогательные поведения извлекаются в набор классов посетителей. Также посетители позволяют хранить связанные операции вместе, определяя их в одном классе.

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

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

Реализация

Для части реализации мы будем использовать шаблон проектирования Visitor на уже реализованной сложной структуре объектов, который был введен с шаблоном проектирования Composite. На мой взгляд, это был бы отличный пример того, как разные шаблоны проектирования могут дополнять друг друга и как повторно использовать / расширять уже существующую кодовую базу.

Наша сложная объектная структура - это файловая система, состоящая из каталогов и файлов различных типов (аудио, видео, текст и т. Д.). Допустим, такая структура уже реализована с использованием шаблона проектирования Composite. Теперь мы хотим добавить возможность экспортировать такую ​​файловую структуру в двух разных форматах: удобочитаемом (просто укажите каждый файл в одном отформатированном списке) и XML.

Первый возможный подход к реализации этой функции - определить метод экспорта для каждого конкретного типа файла. В данном случае это неправильно по нескольким причинам:

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

Как вы могли догадаться, эти проблемы можно легко решить, применив шаблон проектирования Visitor и определив каждую конкретную опцию экспорта в отдельном классе посетителей, который заботится обо всех конкретных деталях реализации для всех типов файлов в одном месте. Давайте сначала проверим диаграмму классов, а затем реализуем шаблон!

Диаграмма классов

На диаграмме классов ниже показана реализация шаблона проектирования Visitor:

IFile определяет общий интерфейс для классов File и Directory:

  • getSize () - возвращает размер файла;
  • render () - отображает пользовательский интерфейс компонента;
  • accept () - делегирует запрос посетителю.

Класс File реализует методы getSize () и render (), дополнительно содержит title, fileExtension , size и icon.

AudioFile, ImageFile, TextFile и VideoFile - это конкретные классы файлов, реализующие accept () из интерфейса IFile и содержит дополнительную информацию о конкретном файле.

Directory реализует те же обязательные методы, что и File, но также содержит title, level, isInitiallyExpanded свойства и список файлов, содержащий объекты IFile. Он также определяет метод addFile (), который позволяет добавлять объекты IFile в каталог (список files). Так же, как и в определенных классах файлов, здесь также реализован метод accept ().

IVisitor определяет общий интерфейс для определенных классов посетителей:

  • getTitle () - возвращает заголовок посетителя, который используется в пользовательском интерфейсе;
  • visitDirectory () - определяет метод посещения для класса Directory;
  • visitAudioFile () - определяет метод посещения для класса AudioFile;
  • visitImageFile () - определяет метод посещения для класса ImageFile;
  • visitTextFile () - определяет метод посещения для класса TextFile;
  • visitVideoFile () - определяет метод посещения для класса VideoFile.

HumanReadableVisitor и XmlVisitor - это классы конкретных посетителей, которые реализуют методы посещения для каждого конкретного типа файлов.

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

IFile

Интерфейс, определяющий методы, которые будут реализованы конкретными файлами и каталогами. Интерфейс также определяет метод accept (), который используется для реализации шаблона проектирования Visitor. Язык Dart не поддерживает интерфейс как тип класса, поэтому мы определяем интерфейс, создавая абстрактный класс и предоставляя заголовок метода (имя, тип возвращаемого значения, параметры) без реализации по умолчанию.

Файл

Конкретная реализация интерфейса IFile. В классе File метод getSize () просто возвращает размер файла, render () - возвращает виджет пользовательского интерфейса файла, который используется в примере. экран.

Классы бетонных файлов

Все классы конкретных типов файлов реализуют метод accept (), который делегирует запрос методу конкретного посетителя.

  • AudioFile - определенный класс файла, представляющий тип аудиофайла, который содержит дополнительное свойство albumTitle.

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

  • TextFile - определенный класс файла, представляющий тип текстового файла, который содержит дополнительное свойство content.

  • VideoFile - определенный класс файла, представляющий тип видеофайла, который содержит дополнительное свойство directionBy.

Каталог

Конкретная реализация интерфейса IFile. Аналогично классу File, render () возвращает виджет пользовательского интерфейса каталога, который используется в примере экрана. Однако в этом классе метод getSize () вычисляет размер каталога, вызывая метод getSize () для каждого элемента в списке files и добавляя результаты. Кроме того, класс реализует метод accept (), который делегирует запрос методу конкретного посетителя для каталога.

Расширения форматирования

Определяет метод расширения indentAndAddNewLine, который добавляет вкладки nTab в начале и символ новой строки в конце String.

IV Посетитель

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

Конкретные посетители

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

  • XmlVisitor - реализует конкретного посетителя, который предоставляет информацию о файлах каждого типа в формате XML.

Пример

Прежде всего, подготавливается файл разметки, который предоставляется как описание шаблона:

Виджет VisitorExample содержит метод buildMediaDirectory (), который строит файловую структуру для примера. Кроме того, он содержит список различных посетителей и предоставляет его виджету FilesVisitorSelection, где индекс конкретного посетителя выбирается путем запуска метода setSelectedVisitorIndex ().

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

Как видно из примера, при выборе конкретного посетителя (экспорт в текстовом формате или в XML) структура файла экспортируется в соответствующий текстовый формат и предоставляется пользователю.

Все изменения кода для шаблона проектирования Visitor и его пример реализации можно найти здесь.

Другие статьи из этой серии

Ваш вклад

👏 Нажмите кнопку хлопка ниже, чтобы выразить свою поддержку и побудить меня писать лучше!
💬 Оставьте отзыв на эту статью, поделившись своими мыслями, комментариями или пожеланиями относительно серии.
📢 Поделитесь этой статьей со своими друзья, коллеги в социальных сетях.
➕ Подписывайтесь на меня на Medium.
⭐ Пометьте репозиторий Github.