Когда Вы решаете использовать посетителей к своим объектам?

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

15
задан Dave Schweisguth 14 February 2016 в 00:00
поделиться

9 ответов

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

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

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

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

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

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

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

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

6
ответ дан 1 December 2019 в 01:53
поделиться

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

7
ответ дан 1 December 2019 в 01:53
поделиться

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

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

4
ответ дан 1 December 2019 в 01:53
поделиться

Я часто использую его, когда обнаруживаю, что хочу поместить метод, который будет государственным, в Entity/DataObject/BusinessObject, но я действительно не хочу вводить эту государственность в мой объект. С этой задачей может справиться stateful visitor, или сгенерировать коллекцию stateful executor объектов из моих non-stateful data объектов. Особенно полезно, когда обработка работы будет передана потокам-исполнителям: множество stateful visitor/workers могут ссылаться на одну и ту же группу non-stateful объектов.

3
ответ дан 1 December 2019 в 01:53
поделиться

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

3
ответ дан 1 December 2019 в 01:53
поделиться

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

2
ответ дан 1 December 2019 в 01:53
поделиться

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

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

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

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

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

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

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

3
ответ дан 1 December 2019 в 01:53
поделиться

Когда у вас возникает следующая проблема:

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

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

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

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

3
ответ дан 1 December 2019 в 01:53
поделиться

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

Рассмотрим следующий сценарий:

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. Задача несколько отличается от приведенного выше примера, но демонстрирует суть паттерна Visitor.

//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);
  }
}
1
ответ дан 1 December 2019 в 01:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: