【问题标题】:Visitor pattern: avoid casting for simple expression calculator访客模式:避免转换为简单的表达式计算器
【发布时间】:2015-09-03 22:44:51
【问题描述】:

我正在尝试为简单的语言解释器设计一个abstract syntax tree。为了避免在评估期间进行强制转换,我尝试使用泛型(此技术在here 中有更全面的描述)。

我的基本类型是Exp<T>,我使用IExpression 作为分组接口(见下文——这将是评估返回的内容)。

public interface IExpression
{
}

public abstract class Exp<T> : IExpression
{ 
    public abstract U Accept<U>(IVisitor<U> visitor);

    public IExpression Eval()
    {
        return this.Accept<IExpression>(new EvaluationVisitor());
    }
}

public class Lit : Exp<int>
{
    public int value;

    public Lit(int value) 
    { 
        this.value = value; 
    }

    public override U Accept<U>(IVisitor<U> visitor)
    {
        return visitor.Visit(this);
    }
}

public class Plus : Exp<int>
{
    public Exp<int> e1, e2;

    public Plus(Exp<int> e1, Exp<int> e2)
    { 
        this.e1 = e1; 
        this.e2 = e2; 
    }

    public override U Accept<U>(IVisitor<U> visitor)
    {
        return visitor.Visit(this);
    }
}

我曾希望我现在可以使用visitor design pattern 来评估树。然而,Plus 表达式的求值会产生丑陋的演员表。有什么办法可以避免吗?

public interface IVisitor<T>
{
    T Visit(Lit exp);
    T Visit(Plus exp);
}

public class EvaluationVisitor : IVisitor<IExpression>
{
    public IExpression Visit(Lit exp)
    {
        return exp;
    }

    public IExpression Visit(Plus exp)
    {
        var v1 = (Lit)exp.e1.Accept<IExpression>(this);
        var v2 = (Lit)exp.e2.Accept<IExpression>(this);
        return new Lit(v1.value + v2.value);
    }
}

【问题讨论】:

  • 我是否认为ExpExpression 的缩写,而LitLiteral 的缩写?
  • @ClickRick 是的,您是正确的(此代码是从 Microsoft 文章链接中复制和修改的)。

标签: c# generics interpreter visitor-pattern


【解决方案1】:

与其尝试使用来自Visit 的返回值(这总是需要强制转换),不如考虑给您的访问者一些状态:

public class EvaluationVisitor : IVisitor<IExpression>
{
    public IExpression Visit(Lit exp)
    {
        mValueStack.Push( exp.value );
        return exp;
    }

    public IExpression Visit(Plus exp)
    {
        exp.e1.Accept<IExpression>( this );
        exp.e2.Accept<IExpression>( this );
        int v2 = mValueStack.Pop();
        int v1 = mValueStack.Pop();
        mValueStack.Push( v1 + v2 );
        return new Lit( v1 + v2 );
    }

    public int Value
    {
        get
        {
            if( mValueStack.Count != 1 )
            {
                // Malformed expression, could throw an exception or something
            }
            return mValueStack.Peek();
        }
    }

    private readonly Stack<int> mValueStack = new Stack<int>();
}

您也可以让您的 EvaluationVisitorVisit 方法返回实际值:

public class EvaluationVisitor : IVisitor<int>
{
    public int Visit(Lit exp)
    {
        return exp.value;
    }

    public int Visit(Plus exp)
    {
        int v1 = exp.e1.Accept<int>( this );
        int v2 = exp.e2.Accept<int>( this );
        return v1 + v2;
    }
}

【讨论】:

  • 如果我想添加另一种类型,If&lt;T&gt; 代表条件执行怎么办?我不只是想要一个int 访问者,我需要更通用的东西(这就是我使用IExpression 作为访问者的返回类型的原因)。
  • @rookie 在这种情况下,请使用第一个选项并提供访问者状态。根据我的经验,尝试在访问者模式中使用通用返回值很少是一个好主意,它只是匆忙变得混乱。传统上,访问者通过将所述值存储在调用者可以访问的成员中来“返回”值。这也是如何将附加参数“传递”到访问方法中的方式。
  • 另一个选项是添加另一个访问者类型来评估条件,因为这实际上是一个布尔评估。如果您尝试使用一个可以将任何表达式评估为任意类型的评估器,您将度过一段糟糕的时光。
  • 我认为最好的结果是当Accept/Visit 方法是无效方法时。如果我们这样做,并且让IVisitor 实现只是改变一些状态而不是返回一个值,我们可以给Accept 方法提供IVisitor 的任何实现。这使我们能够以访问者的身份实现格式化、类型检查和其他算法,只需实现IVisitor。这种水平的可互换性远远好于所讨论的原始实现,我认为更符合算法实际试图实现的精神。
猜你喜欢
  • 1970-01-01
  • 2015-08-02
  • 1970-01-01
  • 1970-01-01
  • 2019-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多