【问题标题】:Visitor pattern and logical operations访问者模式和逻辑操作
【发布时间】:2016-03-18 20:57:30
【问题描述】:

好的,我一直在努力解决我遇到的这个问题。

我创建了一个带有逻辑节点的树结构,例如和,或,等于,介于。 我不希望这些节点只为访问者提供一个接受方法,因为会有更多具有不同实现的访问者。

例如,我正在访问 and。它首先访问自己,所以我知道我正在处理一个和,然后它访问左右节点。可以是前面提到的任何节点。

问题是,我需要一种方法来检查我是否完成了对子节点的访问。 例如我想要这个输出

“与(等于()/或(等于()/介于())”

但由于我无法知道孩子们何时被拜访过,所以会是这样。

"And()Equals()/Or()Equals()/Between()

关于如何克服这个问题的任何建议?

【问题讨论】:

  • 检查您是否已完成访问子节点?你是什​​么意思?你不是在.accept() 通话结束时完成了吗?你是异步做的吗?

标签: c# design-patterns visitor-pattern


【解决方案1】:

因此,当访问节点时,您需要跟踪您在树中的位置。为了能够做到这一点,您需要引入某种上下文。

类似这样的:

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);
    }
}

现在你知道你什么时候在树上左右移动了,但你仍然不知道你在树上的什么位置。为此,让我们引入一个 lineage/breadcrumbs 来跟踪我们当前的路径。

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();
    }
}

现在您应该能够通过层次结构跟踪访问。这要归功于递归调用,它会在遍历层次结构时继续构建面包屑。

【讨论】:

    【解决方案2】:

    感谢 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) + ")";
    }
    

    所以我的程序只接受根节点并打印出我之前提到的字符串。

    【讨论】:

    • 欢迎来到 Stack Overflow。如果您喜欢 @jgauffin 的答案,您应该投票和/或接受它。
    猜你喜欢
    • 1970-01-01
    • 2020-12-18
    • 1970-01-01
    • 2022-07-10
    • 1970-01-01
    • 2012-04-25
    • 2012-01-20
    • 2011-03-22
    • 1970-01-01
    相关资源
    最近更新 更多