Шаблон посетителя и логические операции

Хорошо, я боролся с этой проблемой, которая у меня возникла.

Я создал древовидную структуру с логическими узлами, например. И, Или, Равно, Между. Я не хочу, чтобы эти узлы имели что-то большее, чем метод accept для посетителя, потому что будет больше посетителей с разными реализациями.

Так, например, я посещаю и. Сначала он посещает себя, поэтому я знаю, что имею дело с и, затем он посещает левый и правый узлы. Это может быть любой из ранее указанных узлов.

Проблема в том, что мне нужен способ проверить, что я закончил посещение дочерних узлов. Например, я хочу, чтобы этот вывод

"И(Равно()/Или(Равно()/Между())"

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

"И()Равно()/Или()Равно()/Между()

Любые предложения о том, как преодолеть эту проблему?


person JayRone    schedule 17.03.2016    source источник
comment
Проверка того, что вы закончили посещение дочерних узлов? Что ты имеешь в виду? Разве вы не закончили разговор в конце .accept()? Вы делаете это асинхронно?   -  person Jorn Vernee    schedule 17.03.2016


Ответы (2)


Поэтому вам нужно отслеживать, где вы находитесь в дереве, когда посещаете узлы. Чтобы иметь возможность сделать это, вам нужно ввести какой-то контекст.

Что-то вроде этого:

public class VisitContext
{
    // Visited node
    public VisitedType Visited { get; set; }

    // Visited node is the left child node
    public bool IsLeftNode { get; set; }

   // Visited node is the right child node
    public bool IsRightNode { get; set; }
}

Итак, посетитель получил этот контракт:

public interface IVisitor
{
    public void Visit(VisitContext context);
}

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

public class VisitedType
{
    public void Accept(IVisitor visitor)
    {
        var context = new VisitContext{ Visited = this };
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);
    }

    protected void AcceptAsLeft(VisitContext context)
    {
        context.IsLeftNode=true;
        context.IsRightNode=false;
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);
    }

    protected void AcceptAsRight(VisitContext context)
    {
        context.IsLeftNode=false;
        context.IsRightNode=true;
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);
    }
}

Теперь вы знаете, когда вы идете влево и вправо по дереву, но вы все еще не знаете, где вы находитесь в дереве. Для этого давайте введем линейку/хлебные крошки, которые отслеживают наш текущий путь.

public class VisitContext
{
    public VisitedType Visited { get; set; }
    public bool IsLeftNode { get; set; }
    public bool IsRightNode { get; set; }

    // this.
    public LinkedList<VisitedType> Lineage { get; set; }       
}

И обновите посещенный тип:

public class VisitedType
{
    public void Accept(IVisitor visitor)
    {
        var context = new VisitContext{ Visited = this, Lineage = new LinkedList<VisitedType>() };
        context.Lineage.AddLast(this);
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);
    }

    protected void AcceptAsLeft(VisitContext context)
    {
        //add a bread crumb
        context.Lineage.AddLast(this);
        context.IsLeftNode=true;
        context.IsRightNode=false;
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);

        //remove us when we've visited our children
        context.Lineage.RemoveLast();
    }

    protected void AcceptAsRight(VisitContext context)
    {
        //add a bread crumb
        context.Lineage.AddLast(this);
        context.IsLeftNode=false;
        context.IsRightNode=true;
        visitor.Visit(context);

        _leftNode.AcceptAsLeft(context);
        _rightNode.AcceptAsRight(context);

        //remove us when we've visited our children
        context.Lineage.RemoveLast();
    }
}

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

person jgauffin    schedule 18.03.2016

Спасибо за советы jgauffin! Но у меня есть что-то меньшее, работающее так, как я хочу.

Начал работать с дженериками.

public abstract class Node
{
    public abstract T Accept<T>(IVisitor<T> visitor);
}

public interface IVisitor<T>
{
    T Visit(And element);
    T Visit(Or element);
    T Visit(Equals element);
}

Таким образом, мой посетитель может реализовать его, не заботясь о таких объектах:

public string Visit(Or element)
{
   return "Or(" + element.Left.Accept(this) + "," + element.Right.Accept(this) + ")";
}

Итак, моя программа просто принимает корневой узел и выводит строку, о которой я упоминал ранее.

person JayRone    schedule 18.03.2016
comment
Добро пожаловать в Stack Overflow. Если вам понравился ответ от @jgauffin, вы должны проголосовать за него и/или принять его. - person Fuhrmanator; 18.03.2016