• 运算表达式类的原理及其实现


    运算表达式类的原理及其实现

    运算表达式在编程中是一个很重要的概念,但是实际工作中,需要使用到运算表达式的机会并不太多。但是日前在研究报表系统的时候,发现了它的用处,于是就研究了一下,做了一个比较运算表达式类。

    我的表达式类的实现主要使用了visitor模式。这个类的实现核心分为两个部分,一个部分是具体的表达式类,比如 AddExpression,EqExpression类,他们的工作就是保存语义,把 +、-、*、/等操作记住。另外一部分就是具体的转换类,这里叫做Calculator,他们负责把表达式运算为所需要的结果,通过使用不同的Calculator,我们可以运算出整个表达式的值,也可以把表达式转化为其他的表示形式,比如xml格式等等。我们先看一个例子:

    [代码1]

     void Sample
     
    {
      Expression expr;
      ICalculator cal 
    = new Expressions.AlgorithmCalculator();
      IContext context 
    = Expression.DefaultContext;

      expr 
    = (Expression)3 + (Expression)4;
      Console.WriteLine(expr.ToXml());
      
      
    string text = "<add><int>3</int><int>4</int></add>";
      expr 
    = Expression.FromXml(text);

      Console.WriteLine(expr.ToXml());
     }

    这个例子是从我的测试用例中摘录下来的。表达式类的基本用法就和代码中的表达式形式差不多,目的是为了直观和简单使用。代码1中的工作是把表达式变成xml形式并从xml形式中重新构造一个新的表达式。虽然从中我们不能很明显看出表达式的两个部分的关系,但是这一段代码应该可以让你看得很明显,这是Expression.FromXml和ToXml的实现代码:

    [代码2]

      static public Expression FromXml(XmlNode root, )
      
    {
       XmlExpression xe 
    = new XmlExpression(root);
       Expression expr 
    = new Expression(xe);

       
       
    return (Expression) xe.Evaluate(new FromXmlCalculator(), new XmlContext(expr));
      }


      
    public DataElement ToDataElement()
      
    {
       
    return (DataElement)Evaluate(new XmlCalculator(), new XmlContext(this));
      }

      
      
    public string ToXml()
      
    {
       DataElement element 
    = ToDataElement();
       
    return element.ToString();
      }




    大家从代码2中就可以看到,原来Expression的ToXml和FromXml就是用专门的Calculator来实现出来的。既然Calculator可以实现这样的效果,其他效果我们也一定可以实现。在文章的源代码中,我提供了两个实现:MSSQLCalaulator负责把表达式变成Microsoft SQL Server 2000格式的sql语句,HibernteExpressionCalculator负责把表达式变成NHibernate的表达式形式。

    我是如何实现的?如果您有好奇心的话,一定会想这样问我。别着急,这就给您慢慢道来。首先看一下所有表达式类的基类定义:

    [代码3]

     /// <summary>
     
    /// 抽象的运算表达式接口
     
    /// </summary>

     public interface IOperation
     
    {
      
    /// <summary>
      
    /// 计算表达式的值
      
    /// </summary>
      
    /// <param name="cal">计算器</param>
      
    /// <returns>计算之后的值</returns>
      
    /// <remarks>
      
    /// 计算器是专门根据不同类型的需求,实际进行操作运算的接口
      
    /// </remarks>

      object Evaluate( ICalculator cal, IContext context ); 
     }


    从这个接口我派生出来了ConstValueExpression,UnaryExpression,BinaryExpression,TripleExpression。他们之间的差别仅仅是内涵的参数不同而已,分别是0个,1个,2个和3个参数,用来避免我写重复代码而已,并不重要,重要的是IOperation中的函数Evaluate。

    Evaluate是最重要的函数,它负责传递给表达式Calculator和Context,上文已经说过Calculator的用处了,您可以把Context看成是一个参数的容器,因为IOperation或者Calculator在工作的时候,或多或少需要知道一些调用环境的特殊信息,Context的作用就是把这些信息传过来,至于到底传哪些信息,就看使用者您的需要了,大家看IContext的接口定义可以知道的更清楚:

    [代码4]

     /// <summary>
     
    /// 负责为计算时候提供额外的信息
     
    /// </summary>

     public interface IContext
     
    {
     }



    原来接口IContext定义里边就是空的,不过为了实现上文的Expression的XML存取,我倒是实现了自己专用的Context,它负责把Calculator不认识的表达式或者xml节点放到事件中撒播出去,让用户自己去处理,我的 Context 如下:

    [代码5]

      public class XmlContext : IContext
      
    {
       Expression m_Expr;
       
    private XmlNode m_Node;

       
    /// <summary>
       
    /// 当前的节点
       
    /// </summary>

       public XmlNode Node get return m_Node; } set { m_Node = value; } }

       
    public XmlContext(Expression expr){m_Expr = expr;}

       
    public IOperation UnknownXmlNode(string operation, object[] parameters)
       
    {
        
    return m_Expr.OnUnknownXmlNode(m_Node, operation, this, parameters);
       }


       
    public DataElement UnknownOperation(string operation, object[] parameters)
       
    {
        
    return m_Expr.OnUnknownOperation(operation, this, parameters);
       }

      }



    大家看到了吗?我的这个Context就是把不认识的节点撒播出去,让程序员自己挂接事件来处理这些不认识的东西,它和我专用的Calculator配合的很好:
    [代码6]

      public object Extension(string operation, IContext context, params object[] parameters)
      
    {
       
    if( context is Expression.XmlContext )
       
    {
        Expression.XmlContext xc 
    = (Expression.XmlContext)context;
        
    return xc.UnknownOperation(operation, parameters);
       }


       
    throw new NotSupportedException("不能支持操作:" + operation);
      }




    提了事件当然就不能不提表达式类的扩展性了,我们先看看表达式本身有哪些限制,看一下ICalculator的定义吧:

    [代码7]

     /// <summary>
     
    /// ICalculator 的摘要说明。
     
    /// 负责把表达式变成实际结果的抽象接口
     
    /// </summary>

     public interface ICalculator
     
    {
      
    object Add(object left, object right, IContext context);
      
      
    object Mod(object left, object right, IContext context);

      
    object Eq(object left, object right, IContext context);
      
      
    object LessEq(object left, object right, IContext context);

      
    object And(object left, object right, IContext context);
      
    object Or(object left, object right, IContext context);

      
    object BitAnd(object left, object right, IContext context);
      
    object BitOr(object left, object right, IContext context);
      
    object BitNot(object val, IContext context);

      
    object Plus(object val, IContext context);
      
    object Neg(object val, IContext context);

      
    object Not(object val, IContext context);

      
    object BoolValue(bool val, IContext context);  
      
      
    object DateTimeValue(DateTime val, IContext context);
             
    object MemberRefValue(Member val, IContext context);
             
    object MemberRefValue(LooseMember val, IContext context);

      
    object Extension(string operation, IContext context, params object[] parameters);
         }




    熟悉设计模式的大虾立马就可以看出来了,典型的visitor模式。ICalculator实现了几乎所有的运算操作接口,而那些AddExpression之类的东西所作的事情无非就是把自己的参数传递给ICalculator适当的接口函数中去而已。既然是visitor模式,当然ICalculator就有visitor模式固有的缺陷:扩展性不好!

    如果我要实现一个between的操作,我就要来修改 ICalculator 接口的定义,增加一个接口
      object Between(object cond, object low, object high, IContext);
    然后还要到每一个实现了ICalculator的类中增加这个Between的实现,哦,这样做简直就是噩梦!为了避免这个噩梦,我在ICalculator中增加了Extension接口函数,让所有自己定义的表达式都来调用Extension。看看我扩展的BetweenExpression吧:
    [代码8]

     public class BetweenExpression
      : TripleExpression
     
    {
      
      
      
    override public object Evaluate( ICalculator cal, IContext context )
      
    {
       
    return cal.Extension( BETWEEN, context
           ,
    base.m_Left.Evaluate(cal, context)
           ,
    base.m_Middle.Evaluate(cal, context)
           ,
    base.m_Right.Evaluate(cal, context), context);
      }

     }


    然后在实现自己的Calculator中把Extension实现一下就可以了,我的 MSSQLCalculator 的实现如下:
    [代码9]

      public object Extension(string operation, IContext context, params object[] parameters)
      
    {
       
    if( operation == LikeExpression.LIKE )
        
    return Like(parameters[0], parameters[1], context);
       
    else if( operation == BetweenExpression.BETWEEN )
        
    return Between(parameters[0], parameters[1], parameters[2], context);
        
       
    else if( context is Expressions.ICallbackContext )
        
    return ((ICallbackContext)context).OnCalculateExtension(operation, parameters);

       
    throw new NotSupportedException("不能支持操作:" + operation);
      }


      
    大家要注意代码9中的第二个else if语句,对 ICallbackContext 的回调一定要加上,这样就可以支持其他形式的扩展了。至于如何扩展法,不妨自己动手试一下吧。

    附带源代码到这里下载。

  • 相关阅读:
    PHP保留小数的相关方法
    ASP.NET Core MVC 之过滤器(Filter)
    ASP.NET Core MVC 之控制器(Controller)
    ASP.NET Core MVC 之视图组件(View Component)
    ASP.NET Core MVC 之局部视图(Partial Views)
    标签助手(TagHelper)
    ASP.NET Core MVC 之布局(Layout)
    ASP.NET Core MVC 之视图(Views)
    ASP.NET Core MVC 之模型(Model)
    九卷读书:淘宝从小到大的发展 -重读《淘宝技术这十年》
  • 原文地址:https://www.cnblogs.com/BigTall/p/164183.html
Copyright © 2020-2023  润新知