Некоторые мысли о паттерне 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

      Пример из жизни
      OutlookVisitor
      public 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);
      
      }
      

      OutlookElement
      public interface OutlookElement {
          
          void accept(OutlookVisitor visitor) throws IOException;
      
      }
      

      ItemElement
      public class ItemElement implements OutlookElement {
          private final Method visit;
          protected final T item;
      
          public ItemElement(T item) {
              this.item = item;
              Class 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;
          }
      }
      

      FolderElement
      public 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.

  • 30 июня 2017 в 23:04

    0

    Visitor — хороший паттерн, но проблемы начинаются, когда нужно расширять количество обходимых классов в другой сборке, отличной от сборки, в которой определен сам Visitor. Тогда в этих наследниках приходится ожидать один тип Visitor’а и делать cast к наследнику, определенному в этой сборке, что не очень приятно. В такой ситуации уже удобнее бывает просто сделать Dictioary. С таким подходом потеряем в производительности и возможно в читабельности (хотя и не факт, если такой подход используется часто в кодовой базе), зато получим больше гибкости.
    • 30 июня 2017 в 23:09

      0

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

      Спасибо, я как раз про это написал в разделе «недостатки», в конце.

© Habrahabr.ru