Некоторые мысли о паттерне Visitor
Комментарии (19)
30 июня 2017 в 18:21
0↑
↓
Я может чего то не понял, но чем плох самый первый предложенный вариант — когда объект сам решает, как ему считать свою площадь, рисоваться и прочая прочая?
По моему именно такая реализация и ожидается от ООП модели.30 июня 2017 в 19:10
0↑
↓
И каждый раз, когда понадобится новый функционал, добавлять методы? Это нарушение ocp. А если потом нам фигуры понадобятся в проекте, где не нужны добавленные нами возможности?30 июня 2017 в 19:34
0↑
↓
Написать экстеншены — плохой вариант?
30 июня 2017 в 19:49
+1↑
↓
Экстеншны к чему? У нас на входе — IFigure, конкретный тип неизвестен. Можно только написать экстеншн к IFigure, но как внутри него мы определим, какая именно фигура у нас?
30 июня 2017 в 23:08
0↑
↓
А потом понадобятся в другом проекте — глупая отмазка.
Вы либо пишете либу уровня фреймворка, где у вас есть четко определенные цели и задачи и вы их закрываете, либо пишете свой проект, где реализация будет соответствовать требованиям.Пытаться совместить эти две вещи, усложняя реализацию — плохая идея. И да, визитор в данном случае — бессмысленное усложнение, код стал сложнее, пользы — не заметно.
30 июня 2017 в 23:14
0↑
↓
Никто не говорит о преждевременной оптимизации. Здесь на нарочно упрощенном примере показано как сделать расширение. Предполагается что в реальной жизни вам это уже понадобилось, и теперь вы знаете как это сделать.30 июня 2017 в 23:22
0↑
↓
Ок, как скажите. Я же продолжаю считать, что надо всегда стремиться писать переиспользуемый код, и что solid — это полезные рекомендации.
30 июня 2017 в 18:28
0↑
↓
Допустим, у вас реализована эта модель.
Далее понадобилась новая функциональность: считать площадь в определенных единицах измерения на выбор.
Придется добавлять в методы IFiguresVisitor’а новый параметр, который будет абсолютно лишним для WriteToConsoleVisitor и DrawVisitor?30 июня 2017 в 19:00
0↑
↓
Нет конечно. Как вариант, в конструктор визитора передавать единицы измерения. Или на кажый вариант — свой визитор.
30 июня 2017 в 20:11 (комментарий был изменён)
0↑
↓
В программе, сама геометрическая фигура, будет выражена исключительно value object.
Если мне нужно работать с прямоугольником в калькуляторе, то я напишу класс Rectangle, который будет находится в пространстве имен geom и именно он будет работать с тем самым vo. Если мне нужно нарисовать прямоугольник, то я создам класс Rectangle в пространстве имен shape и этот класс будет производить расчеты с помощью класса rectangle из пространства имен geom. Разве нет? Мне вообще кажется, что применять визитер уместно только в структурной архитектуре, как например, деревья, как например рендер или физичиские движки, да и то в самых низкоуровневых реализациях.30 июня 2017 в 20:26
+1↑
↓
Лично мне не очень понятно, зачем создавать разные прямоугольники с разными возможностями, когда можно описать отдельно прямоугольник и отдельно — возможности. Но даже если и так — как вы узнаете, что надо создать именно прямоугольник из geom или shape, если на входе — абстрактная IFigure?30 июня 2017 в 20:47 (комментарий был изменён)
0↑
↓
интерфейс это гарантия наличия api, либо выполнения контракта., а абстракция, это абстракция, которая выражается ключевым словом abstract. То есть для меня словосочетание абстрактная ЯФигура, немного дико.
и прямоугольник один, он выражен членами структуры, которые описывают информационную модель конкретной фигуры. два одноименных класса в разных пространствах это модели содержащие логику. одна считает, другая рисует. Поэтому мне не совсем понятно как в виде класса содержащего Rectangle.getArea может быть совместима с интерфейсом Rectangle.draw. Ну, а так, если это дерево, то визитер, а если что-то другое, то тоже, что-то другое.30 июня 2017 в 22:52
0↑
↓
интерфейс это гарантия наличия api, либо выполнения контракта., а абстракция, это абстракция, которая выражается ключевым словом abstract. То есть для меня словосочетание абстрактная ЯФигура, немного дико.
Не очень понял вашу мысль. У нас есть контракт IFigure, который выполняют все фигуры. Абстрактная фигура — другими словами. Что значит «гарантия наличия апи» и зачем тут абстрактный класс — не понятно.Поэтому мне не совсем понятно как в виде класса содержащего Rectangle.getArea может быть совместима с интерфейсом Rectangle.draw.
Тут тоже не понял, о чем вы.
Впрочем, видение — оно у всех разное. Я лишь описал свои мысли, и не навязывают их. Вы, видимо, по-другому смотрите на эту тему.
30 июня 2017 в 21:19 (комментарий был изменён)
0↑
↓
Но все дело в том, что у нас в голове находится в данный момент. Вы объясняете на примере рисования фигур, каким замечательным является паттерн визитер. Я говорю что он замечательный, когда работает с деревом.
Вот представьте что Вам нужно в программе напрямую работать с прямоугольником — производить расчеты сторон, площади. А вместо этого Вам дадут в руки голую модель и щепотку алгоритмов. Это немного странно.
, но если Вы создаете что-то крутое, как например физический движок или библиотеку рендера в играх, то это самое то.А статья классная, кратко, но очень информативно.
30 июня 2017 в 22:56
0↑
↓
Конечно, модель должна зависеть от задачи и от выбранного подхода к её решению.Спасибо, приятно!
30 июня 2017 в 22:49 (комментарий был изменён)
0↑
↓
Паттерн Visitor позволяет сделать универсальный обход структуры. Он становится полезен, когда мы имеем не один объект из семейства, а структуру из множества объектов. Например, внутри треугольника могут лежать квадраты, в которых есть другие треугольники. А у вас Visitor знает только то, как работать с одним треугольником и с одним квадратом. То, как они друг в друга входят, посетитель не знает. Как только Visitor обработал одну фигуру, управление передается в метод accept, а там уже наш Visitor получит для обработки следующую фигуру.Основная идея данного приема — избавиться от god-метода со свитчом:
switch (figure.type) { case Triangle: visitTriangle(figure); break; case Circle: visitCircle(figure); break; case Rectangle(figure); visitRectangle(figure); break; }
Если figure — простая фигура, то этот switch можно расширять до бесконечности и особых проблем не заметить. Но если figure состоит из других фигур, то внутри методов visitRectangle, visitCircle, visitTriangle придется сделать отдельные свитчи, и вот тут-то уже возникают реальные сложности, когда можно в 3 объектах запутаться. И тогда на помощь приходит Visitor.
30 июня 2017 в 23:40
0↑
↓
Пример из жизниOutlookVisitorpublic interface OutlookVisitor { void visitFolder(FolderElement folder); void visitAppointment(ItemElement
appointment); void visitCalendarMessage(ItemElement calendarMessage); void visitReportMessage(ItemElement reportMessage); void visitTaskRequest(ItemElement taskRequest); void visitMessage(ItemElement message); void visitItem(ItemElement - item); void visitTask(ItemElement
task); void visitPost(ItemElement post); void visitNote(ItemElement note); void visitContact(ItemElement contact); void visitDistributionList(ItemElement distributionList); void visitDocument(ItemElement document); void visitJournal(ItemElement journal); } OutlookElementpublic interface OutlookElement { void accept(OutlookVisitor visitor) throws IOException; }
ItemElementpublic class ItemElement
implements OutlookElement { private final Method visit; protected final T item; public ItemElement(T item) { this.item = item; Class extends Item> cl = item.getClass(); String methodName = "visit" + cl.getSimpleName(); try { this.visit = OutlookVisitor.class.getMethod(methodName, ItemElement.class); } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) { throw new IllegalArgumentException(e); } } @Override public void accept(OutlookVisitor visitor) throws IOException { try { visit.invoke(visitor, this); } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new IOException(e); } } public T getItem() { return item; } } FolderElementpublic class FolderElement implements OutlookElement { private final Folder folder; public FolderElement(Folder folder) { this.folder = folder; } @Override public void accept(OutlookVisitor visitor) throws IOException { int itemCount = folder.getItemCount(); List
- items = folder.getItems(0, itemCount); for (Item item: items) { OutlookElement element = new ItemElement<>(item); element.accept(visitor); } List
folders = folder.getFolders(); for (Folder child: folders) { FolderElement folderElement = new FolderElement(child); folderElement.accept(visitor); } visitor.visitFolder(this); } public Folder getFolder() { return folder; } } Обход всего, что есть в аутлуке, сверху вниз.
Исходная модель данных ничего о визиторах не знает, поэтому вокруг ее объектов сделаны обертки с методами accept.
Чтобы не писать много однотипных методов, при помощи reflection сделан один универсальный метод accept.
Visitor работает с отдельными элементами, содержимое каталога извлекает FolderElement.
- item); void visitTask(ItemElement
30 июня 2017 в 23:04
0↑
↓
Visitor — хороший паттерн, но проблемы начинаются, когда нужно расширять количество обходимых классов в другой сборке, отличной от сборки, в которой определен сам Visitor. Тогда в этих наследниках приходится ожидать один тип Visitor’а и делать cast к наследнику, определенному в этой сборке, что не очень приятно. В такой ситуации уже удобнее бывает просто сделать Dictioary. С таким подходом потеряем в производительности и возможно в читабельности (хотя и не факт, если такой подход используется часто в кодовой базе), зато получим больше гибкости. 30 июня 2017 в 23:09
0↑
↓
Visitor — хороший паттерн, но проблемы начинаются, когда нужно расширять количество обходимых классов в другой сборке, отличной от сборки, в которой определен сам Visitor.
Спасибо, я как раз про это написал в разделе «недостатки», в конце.