Цель шаблона "посетитель" с примерами [дубликат]

82
задан Ravindra babu 5 December 2017 в 12:58
поделиться

4 ответа

Давным-давно ...

class MusicLibrary {
    private Set<Music> collection ...
    public Set<Music> getPopMusic() { ... }
    public Set<Music> getRockMusic() { ... }
    public Set<Music> getElectronicaMusic() { ... }
}

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

interface Visitor<T> {
    visit(Set<T> items);
}

interface MusicVisitor extends Visitor<Music>;

class MusicLibrary {
    private Set<Music> collection ...
    public void accept(MusicVisitor visitor) {
       visitor.visit( this.collection );
    }
}

class RockMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getRockMusic() { return this.picks; }
}
class AmbientMusicVisitor implements MusicVisitor {
    private final Set<Music> picks = ...
    public visit(Set<Music> items) { ... }
    public Set<Music> getAmbientMusic() { return this.picks; }
}

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

65
ответ дан 24 November 2019 в 09:02
поделиться

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

Я никогда не использовал ее, но она была бы полезна для: Реализации определенной функции, которая должна быть выполнена. в разных подклассах, поскольку каждый из подклассов должен реализовывать его по-разному, другой класс будет реализовывать все функции. Вроде как модуль, но только для набора классов. В Википедии есть довольно хорошее объяснение: http://en.wikipedia.org/wiki/Visitor_pattern И их пример помогает объяснить то, что я пытаюсь сказать.

Надеюсь, что это поможет немного прояснить ситуацию.

РЕДАКТИРОВАТЬ ** Извините, я связался с википедией для вашего ответа, но у них действительно есть достойный пример :) Не пытаюсь быть тем парнем, который говорит, иди и найди сам.

6
ответ дан 24 November 2019 в 09:02
поделиться

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

1
ответ дан 24 November 2019 в 09:02
поделиться

Итак, вы, вероятно, прочитали миллиард различных объяснений паттерна visitor, и вы, вероятно, все еще говорите: "Но когда бы вы его использовали!"

Традиционно visitors используются для реализации тестирования типов без ущерба для безопасности типов, при условии, что ваши типы хорошо определены и известны заранее. Допустим, у нас есть несколько классов:

abstract class Fruit { }
class Orange : Fruit { }
class Apple : Fruit { }
class Banana : Fruit { }

И допустим, мы создаем Fruit[]:

var fruits = new Fruit[]
    { new Orange(), new Apple(), new Banana(),
      new Banana(), new Banana(), new Orange() };

Я хочу разбить список на три списка, каждый из которых содержит апельсины, яблоки или бананы. Как бы вы это сделали? Ну, простым решением был бы тест типов:

List<Orange> oranges = new List<Orange>();
List<Apple> apples = new List<Apple>();
List<Banana> bananas = new List<Banana>();
foreach (Fruit fruit in fruits)
{
    if (fruit is Orange)
        oranges.Add((Orange)fruit);
    else if (fruit is Apple)
        apples.Add((Apple)fruit);
    else if (fruit is Banana)
        bananas.Add((Banana)fruit);
}

Он работает, но есть много проблем с этим кодом:

  • Для начала, он уродлив.
  • Он не безопасен для типов, мы не поймаем ошибки типов до времени выполнения.
  • Он не удобен для сопровождения. Если мы добавим новый производный экземпляр Fruit, нам придется выполнять глобальный поиск в каждом месте, где выполняется проверка типа fruit, иначе мы можем пропустить типы.

Паттерн Visitor решает эту проблему элегантно. Начнем с модификации нашего базового класса Fruit:

interface IFruitVisitor
{
    void Visit(Orange fruit);
    void Visit(Apple fruit);
    void Visit(Banana fruit);
}

abstract class Fruit { public abstract void Accept(IFruitVisitor visitor); }
class Orange : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }
class Apple : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }
class Banana : Fruit { public override void Accept(IFruitVisitor visitor) { visitor.Visit(this); } }

Похоже, что мы копируем код, но обратите внимание, что все производные классы вызывают различные перегрузки (Apple вызывает Visit(Apple), Banana вызывает Visit(Banana), и так далее).

Реализуйте посетителя:

class FruitPartitioner : IFruitVisitor
{
    public List<Orange> Oranges { get; private set; }
    public List<Apple> Apples { get; private set; }
    public List<Banana> Bananas { get; private set; }

    public FruitPartitioner()
    {
        Oranges = new List<Orange>();
        Apples = new List<Apple>();
        Bananas = new List<Banana>();
    }

    public void Visit(Orange fruit) { Oranges.Add(fruit); }
    public void Visit(Apple fruit) { Apples.Add(fruit); }
    public void Visit(Banana fruit) { Bananas.Add(fruit); }
}

Теперь вы можете разделить ваши фрукты без типового теста:

FruitPartitioner partitioner = new FruitPartitioner();
foreach (Fruit fruit in fruits)
{
    fruit.Accept(partitioner);
}
Console.WriteLine("Oranges.Count: {0}", partitioner.Oranges.Count);
Console.WriteLine("Apples.Count: {0}", partitioner.Apples.Count);
Console.WriteLine("Bananas.Count: {0}", partitioner.Bananas.Count);

Это имеет следующие преимущества:

  • относительно чистый, легко читаемый код.
  • Типовая безопасность, ошибки типов отлавливаются во время компиляции.
  • Удобство сопровождения. Если я добавляю или удаляю конкретный класс Fruit, я могу изменить свой интерфейс IFruitVisitor для соответствующей обработки типа, и компилятор немедленно найдет все места, где мы реализуем интерфейс, чтобы мы могли внести соответствующие изменения.

С учетом сказанного, посетители обычно излишни, они имеют тенденцию сильно усложнять API, и определение нового посетителя для каждого нового типа поведения может быть очень обременительным.

Обычно вместо посетителей следует использовать более простые паттерны, такие как наследование. Например, в принципе, я мог бы написать класс типа:

class FruitPricer : IFruitVisitor
{
    public double Price { get; private set; }
    public void Visit(Orange fruit) { Price = 0.69; }
    public void Visit(Apple fruit) { Price = 0.89; }
    public void Visit(Banana fruit) { Price = 1.11; }
}

Это работает, но в чем преимущество этой тривиальной модификации:

abstract class Fruit
{
    public abstract void Accept(IFruitVisitor visitor);
    public abstract double Price { get; }
}

Итак, вы должны использовать посетителей, если выполняются следующие условия:

  • У вас есть четко определенный, известный набор классов, которые будут посещаться.

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

  • Вы выполняете операции над классом объектов и хотите избежать тестирования типов во время выполнения. Обычно это происходит, когда вы обращаетесь к иерархии разрозненных объектов с различными свойствами.

Не используйте посетителей, когда:

  • Вы поддерживаете операции над классом объектов, производные типы которых не известны заранее.

  • Операции над объектами хорошо определены заранее, особенно если они могут быть унаследованы от базового класса или определены в интерфейсе.

  • Клиентам проще добавлять новые функциональные возможности в классы, использующие наследование.

  • Вы просматриваете иерархию объектов, которые имеют одинаковые свойства или интерфейс.

  • Вам нужен относительно простой API.

190
ответ дан 24 November 2019 в 09:02
поделиться
Другие вопросы по тегам:

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