Использование шаблона посетителя с обобщениями в C # - PullRequest
8 голосов
/ 03 февраля 2009

Я хочу знать, является ли приведенное ниже допустимое использование шаблона посетителя. Мне немного неудобно возвращаться из вызовов Accept () или Visit () - это правильное использование этого шаблона, и если нет, то почему?

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

interface IAnimalElement<T>
{
   T Accept(IAnimalVisitor<T> visitor);
}

interface IAnimalVisitor<T>
{
    T Visit(Lion lion);
    T Visit(Peacock peacock);
    T VisitZoo(List<Animal> animals);
}

abstract class Animal
{
    public int Age { get; protected set; }
}

class Lion : Animal, IAnimalElement<int>
{
    public Lion(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class Peacock : Animal, IAnimalElement<int>
{
    public Peacock(int age)
    {
        Age = age;
    }

    public int Accept(IAnimalVisitor<int> visitor)
    {
        return visitor.Visit(this);
    }
}

class AnimalAgeVisitor : IAnimalVisitor<int>
{
    public int TotalAge { get; private set; }

    int IAnimalVisitor<int>.Visit(Lion lion)
    {
        TotalAge += lion.Age;
        return lion.Age;
    }

    int IAnimalVisitor<int>.Visit(Peacock peacock)
    {
        TotalAge += peacock.Age + 10;
        return peacock.Age + 10; // peacocks ages are always -10y, correct.
    }

    public int VisitZoo(List<Animal> animals)
    {
        // Calculate average animal age.

        int sum = 0;
        int count = 0;
        foreach (IAnimalElement<int> animal in animals)
        {
            sum += animal.Accept(this);
            ++count;
        }

        return count == 0 ? 0 : sum / count;
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Animal> animals = new List<Animal>() { new Lion(10), 
          new Lion(15), new Peacock(3), new Lion(2), new Peacock(9) };

        AnimalAgeVisitor visitor = new AnimalAgeVisitor();

        Console.WriteLine("Average age = {0}, Total age = {1}", 
            visitor.VisitZoo(animals), visitor.TotalAge);
    }
}

Ответы [ 4 ]

4 голосов
/ 04 февраля 2009

Что ж, мне кажется, что реализация немного на заборе.

Либо ваши методы Visit и Accept возвращают void и отслеживают все состояния в объекте Visitor. Допросите это в конце.

или ...

Have Visit и Accept возвращают текущее состояние и функционально принимают входящее текущее состояние.

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

3 голосов
/ 23 декабря 2015

Краткий ответ: Я не вижу проблем с выставлением IVisitor, возвращающего универсальный параметр.
См. Правила FxCop .

Затем разрешается использовать разные IVisitor , каждый из которых возвращает свое значение.

Однако, в вашем случае , Посетитель не полезен, так как каждое животное имеет свойство Возраст , поэтому все можно сделать с помощью Животное или с новым IAnimal интерфейс.

В качестве альтернативы используется многократная отправка за счет потери Строгий набор .

Используйте шаблон Visitor , если вы хотите заменить (или не писать) переключатель следующим образом:

IAnimal animal = ...;
switch (animal.GetType().Name)
{
  case "Peacock":
    var peacock = animal as Peacock;
    // Do something using the specific methods/properties of Peacock
    break;
  case "Lion":
    var peacock = animal as Lion;
    // Do something using the specific methods/properties of Lion
    break;
   etc...
}

или вложенный if-then-else эквивалент.

Его цель - направить экземпляр к подпрограмме, соответствующей его типу, используя полиморфизм, а затем избегать некрасивых операторов if-then-else / switch и ручные броски . Кроме того, это помогает уменьшить связь между несвязанным кодом.

Альтернативой этому является добавление виртуального метода в дерево классов для посещения. Однако иногда это невозможно или нежелательно:

  • код посещаемого класса не модифицируется (например, не принадлежит)
  • код посещаемого класса не относится к коду посещения (добавление его в класс будет означать снижение сцепления класса).

Именно поэтому он часто используется для обхода дерева объектов (html-узлы, лексерные токены и т. Д.). Шаблон Visitor подразумевает следующие интерфейсы:

  • IVisitor

    /// <summary>
    /// Interface to implement for classes visiting others. 
    /// See Visitor design pattern for more details.
    /// </summary>
    /// <typeparam name="TVisited">The type of the visited.</typeparam>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    public interface IVisitor<TVisited, TResult> : IVisitor where TVisited : IVisitable
    {
        TResult Visit(TVisited visited);
    }
    
    /// <summary>
    /// Marking interface.
    /// </summary>
    public interface IVisitor{}
    
  • IVisitable

    /// <summary>
    /// Interface to implement for classes visitable by a visitor.
    /// See Visitor design pattern for more details.
    /// </summary>
    /// <typeparam name="TVisitor">The type of the visitor.</typeparam>
    /// <typeparam name="TResult">The type of the result.</typeparam>
    public interface IVisitable<TVisitor, TResult> : IVisitable where TVisitor : IVisitor
    {
        TResult Accept(TVisitor visitor);
    }
    
    /// <summary>
    /// Marking interface.
    /// </summary>
    public interface IVisitable {}
    

Реализация Принять в каждом IVisitable должен вызывать Визит (это) .

1 голос
/ 02 мая 2011

Доступный метод accept не должен ничего возвращать. В акцепте предполагается только указать посетителю, что посетить после или во время посещения.

1 голос
/ 03 февраля 2009

Это довольно часто. Я не знаю, можете ли вы сделать это в C #, но в Java нормально оставить метод Accept универсальным, так что возвращаемое значение определяется посетителем, а не посетителем:

interface IAnimalElement
{
   <T> T Accept(IAnimalVisitor<T> visitor);
}


interface IAnimalVisitor<T> 
{
   T Visit(Peacock animal);
  ...
}

Для процедур можно использовать IAnimalVisitor<Void>, возвращающее null.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...