Отслеживание элементов шаблона посетителя

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

/// <summary>
/// Defines a type that may accept visitors.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IVisitable<T>
{
    // Methods
    void Accept(T instance, IVisitor<T> visitor);
}

/// <summary>
/// Defines a type that visits objects.
/// </summary>
/// <typeparam name="T"></typeparam>
public interface IVisitor<T>
{
    // Methods
    void Visit(IVisitable<T> visitable);

    // Properties
    bool HasCompleted { get; }
}

person Jeff    schedule 18.01.2011    source источник


Ответы (1)


Посетитель должен отслеживать все элементы, которые он посетил. Посетитель всегда знает, что он посещает, в отличие от IVisitable, который знает только то, что его можно посетить.

Любое другое решение увеличило бы связь.

Что касается ваших интерфейсов, я бы изменил их так, чтобы они выглядели так:

public interface IVisitable<T>
{
    void Accept(IVisitor<T> visitor);
}

public interface IVisitor<T>
{
    bool Visit(T item);
}

Это означает, что посетитель должен вести список посещенных элементов, если он не может обрабатывать один и тот же элемент более одного раза:

public class MyVisitor : IVisitor<TheItem>
{
    private List<TheItem> _visitedItems = new List<TheItem>();

    public bool Visit(TheItem item)
    {
         if (_visitedItems.Contains(item)) return true;
         _visitedItems.Add(item);

         //process here. Return false when iteration should be stopped.
    }
}

public class MyItems : IVisitable<TheItem>
{

     public void Accept(IVisitor<TheItem> visitor)
     {
         foreach (var item in items)
         {
             if (!visitor.Visit(item))
                 break;
         }
     }
}

Обновление 2

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

Обновление 3

Вы можете создать список: List<MyItem> items = new List<MyItem>(); и повторять его, используя оператор foreach (который использует интерфейс IEnumerable<T>):

foreach (var item in items)
{
    //do anything here. use `break` to exit loop.
}

Это то же самое, что:

var enumerator = items.GetEnumerator();
while (enumerator.MoveNext())
{
    Console.WriteLine("The item: " + enumerator.Current);
}
person jgauffin    schedule 18.01.2011
comment
Я не уверен, как это сделать, учитывая интерфейсы выше. Должен ли IVisitor иметь метод Visit(T instance), а не Visit(IVisitable‹T› visitable)? Или он должен принимать как экземпляр, так и посещаемый объект? - person Jeff; 18.01.2011
comment
Но как посетитель может узнать, что итерация должна быть остановлена, если список элементов содержится только в посещаемом объекте? Я хочу посетить весь граф, пока не будут посещены все узлы. - person Jeff; 18.01.2011
comment
тогда он должен просто продолжать возвращать значение true до тех пор, пока он больше не будет вызываться. - person jgauffin; 18.01.2011
comment
Верно, но кто должен следить за тем, чтобы один и тот же узел не посещался дважды? Посещаемый? Или я должен просто реализовать IEnumerable? Звучит проще - person Jeff; 18.01.2011
comment
Всегда тот, кто посещает (как в моем примере), должен отслеживать посещенные элементы. Если это не школьное задание, я бы выбрал IEnumerator вместо реализации шаблона посетителя. В сети есть несколько примеров. - person jgauffin; 18.01.2011
comment
Платформа .NET огромна, и иногда бывает трудно найти правильные классы/интерфейсы. Был там, сделал это. - person jgauffin; 18.01.2011