Когда вы решили использовать посетителей для ваших объектов? - PullRequest
15 голосов
/ 12 мая 2010

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

Ответы [ 9 ]

7 голосов
/ 12 мая 2010

Шаблон посетителя особенно полезен при применении операции ко всем элементам довольно сложной структуры данных, для которой обход не является тривиальным (например, обход параллельных элементов или обход сильно взаимосвязанной структуры данных) или при реализации двойного -dispatch. Если элементы должны обрабатываться последовательно и если двойная диспетчеризация не требуется, то, как правило, лучше использовать пользовательские Iterable и Iterator, тем более что они лучше подходят для других API.

6 голосов
/ 02 июня 2010

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

Иногда бывает не удобно иметь все поведения для определенного объекта, определенного в одном классе.Например, в Java, если ваш модуль требует, чтобы метод toXml был реализован в группе классов, первоначально определенных в другом модуле, это сложно, потому что вы не можете написать toXml где-то еще, чем исходный файл класса, что означает, что вы можетене расширять систему без изменения существующих источников (в Smalltalk или других языках вы можете группировать методы по расширению, которые не привязаны к определенному файлу).

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

Объектно-ориентированные языки превосходят в точке 2. Если у вас есть интерфейс, вы можете безопасно и легко добавлять новые реализации.Функциональные языки превосходят в точке 1. Они основаны на сопоставлении с образцом / специальном полиморфизме / перегрузке, поэтому вы можете легко добавлять новые функции к существующим типам.

Шаблон посетителя - это способ поддержки точки 1 в объекте.ориентированный дизайн: вы можете легко расширить систему с новым поведением безопасным для типов способом (что было бы не так, если бы вы выполняли ручное сопоставление с шаблоном if-else-instanceof, потому что язык никогда не предупреждаетВы, если дело не покрыто).

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

Итак, в заключение я бы сказал, что вы были правы в своем анализе :)

PS: шаблон посетителей хорошо работает с составным шаблоном, но они также полезны по отдельности

4 голосов
/ 12 мая 2010

Иногда это просто вопрос организации. Если у вас есть n-виды объектов (т.е. классов) с m-типами операций (т. Е. Методов), хотите ли вы, чтобы n * m пар класс / метод были сгруппированы по классам или по методам? Большинство ОО-языков сильно склонны группировать вещи по классам, но есть случаи, когда организация с помощью операций имеет больше смысла. Например, при многофазной обработке графов объектов, как в компиляторе, часто более полезно думать о каждой фазе (то есть: операции) как о единице, а не думать обо всех операциях, которые могут произойти с конкретным видом узла.

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

3 голосов
/ 03 июня 2010

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

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

Например, рассмотрим XML DOM - Node - это базовый класс, а Element, Attribute и другие типы Node определяют иерархию классов.

Представьте, что необходимо вывести DOM в формате JSON. Поведение не присуще Узлу - если бы это было так, нам бы пришлось добавить методы в Узел для обработки всех форматов, которые могут понадобиться клиенту (toJSON(), toASN1(), toFastInfoSet() и т. Д.). Мы можем даже утверждать, что toXML() здесь не принадлежит, хотя это может быть предоставлено для удобства, так как оно будет использоваться большинством клиентов, и концептуально «ближе» к DOM, поэтому toXML может быть встроен в Node для удобства - хотя это не должно быть, и может обрабатываться как все другие форматы.

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

Реализация каждого метода посетителя обрабатывает форматирование для каждого типа узла. Это может быть сделано, потому что все необходимое состояние доступно из методов каждого типа узла.

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

3 голосов
/ 03 июня 2010

При возникновении следующей проблемы:

Множество различных и несвязанных операций необходимо выполнять над объектами узла в гетерогенной совокупной структуре. Вы хотите избежать «загрязнения» классов узлов этими операциями. И вам не нужно запрашивать тип каждого узла и приводить указатель к правильному типу перед выполнением требуемой операции.

Затем вы можете использовать шаблон Visitor с одним из следующих намерений:

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

http://sourcemaking.com/design_patterns/visitor)

3 голосов
/ 12 мая 2010

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

3 голосов
/ 12 мая 2010

Я часто использую его, когда обнаруживаю, что хочу поместить метод с сохранением состояния в Entity / DataObject / BusinessObject, но я действительно не хочу вводить это состояние в мой объект Посетитель с отслеживанием состояния может выполнить эту работу или создать коллекцию объектов исполнителя с сохранением состояния из моих объектов данных без сохранения состояния. Особенно полезно, когда обработка работы будет передана потокам исполнителя, многие посетители / работники с состоянием могут ссылаться на одну и ту же группу объектов без состояния.

2 голосов
/ 12 мая 2010

Я бы всегда рекомендовал использовать посетителя, когда вы полностью знаете, какие классы реализуют интерфейс. Таким образом, вы не будете делать какие-либо не очень приятные instanceof вызовы, и код станет намного более читабельным. Кроме того, посетитель может быть повторно использован во многих местах, как в настоящем, так и в будущем.

1 голос
/ 08 июня 2010

Шаблон посетителя - это очень естественное решение проблем двойной отправки.Проблема двойной отправки - это подмножество проблем динамической отправки , и она связана с тем, что перегрузки методов определяются статически во время компиляции, в отличие от виртуальных (переопределенных) методов, которые определяются во время выполнения.* Рассмотрим этот сценарий:

public class CarOperations {
  void doCollision(Car car){}
  void doCollision(Bmw car){}
}

public class Car {
  public void doVroom(){}
}      

public class Bmw extends Car {
  public void doVroom(){}
}

public static void Main() {
    Car bmw = new Bmw();

    bmw.doVroom(); //calls Bmw.doVroom() - single dispatch, works out that car is actually Bmw at runtime.

    CarOperations carops = new CarOperations();
    carops.doCollision(bmw); //calls CarOperations.doCollision(Car car) because compiler chose doCollision overload based on the declared type of bmw variable
}

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

//This is the car operations interface. It knows about all the different kinds of cars it supports
//and is statically typed to accept only certain ICar subclasses as parameters
public interface CarVisitor {
   void StickAccelerator(Toyota car);
   void ChargeCreditCardEveryTimeCigaretteLighterIsUsed(Bmw car);
}

//Car interface, a car specific operation is invoked by calling PerformOperation  
public interface Car {

   public string getMake();
   public void setMake(string make);

   public void performOperation(CarVisitor visitor);
}

public class Toyota implements Car {
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(CarVisitor visitor) {
     visitor.StickAccelerator(this);
   }
}

public class Bmw implements Car{
   private string make;
   public string getMake() {return this.make;}
   public void setMake(string make) {this.make = make;}

   public void performOperation(ICarVisitor visitor) {
     visitor.ChargeCreditCardEveryTimeCigaretteLighterIsUsed(this);
   }
}

public class Program {
  public static void Main() {
    Car car = carDealer.getCarByPlateNumber("4SHIZL");
    CarVisitor visitor = new SomeCarVisitor();
    car.performOperation(visitor);
  }
}
...