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

本文介绍运算表达式类的原理及实现。作者在研究报表系统时制作了运算表达式类,采用visitor模式,核心分表达式类和转换类。还展示了表达式类基本用法及相关代码,阐述了实现过程、扩展性问题及解决办法,并提供了源代码下载。

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

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

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

[代码1]

None.gif   void  Sample
ExpandedBlockStart.gifContractedBlock.gif 
dot.gif {
InBlock.gif  Expression expr;
InBlock.gif  ICalculator cal 
= new Expressions.AlgorithmCalculator();
InBlock.gif  IContext context 
= Expression.DefaultContext;
InBlock.gif
InBlock.gif  expr 
= (Expression)3 + (Expression)4;
InBlock.gif  Console.WriteLine(expr.ToXml());
InBlock.gif  
InBlock.gif  
string text = "<add><int>3</int><int>4</int></add>";
InBlock.gif  expr 
= Expression.FromXml(text);
InBlock.gif
InBlock.gif  Console.WriteLine(expr.ToXml());
ExpandedBlockEnd.gif }

None.gif

 

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

[代码2]

None.gif    static   public  Expression FromXml(XmlNode root, dot.gif)
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   XmlExpression xe 
= new XmlExpression(root);
InBlock.gif   Expression expr 
= new Expression(xe);
InBlock.gif
InBlock.gif   dot.gif
InBlock.gif   
return (Expression) xe.Evaluate(new FromXmlCalculator(), new XmlContext(expr));
ExpandedBlockEnd.gif  }

None.gif
None.gif  
public  DataElement ToDataElement()
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   
return (DataElement)Evaluate(new XmlCalculator(), new XmlContext(this));
ExpandedBlockEnd.gif  }

None.gif  
None.gif  
public   string  ToXml()
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   DataElement element 
= ToDataElement();
InBlock.gif   
return element.ToString();
ExpandedBlockEnd.gif  }

None.gif
None.gif



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

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

[代码3]

ExpandedBlockStart.gif ContractedBlock.gif   /**/ /// <summary>
InBlock.gif 
/// 抽象的运算表达式接口
ExpandedBlockEnd.gif 
/// </summary>

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

InBlock.gif  object Evaluate( ICalculator cal, IContext context ); 
ExpandedBlockEnd.gif }

None.gif


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

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

[代码4]

ExpandedBlockStart.gif ContractedBlock.gif   /**/ /// <summary>
InBlock.gif 
/// 负责为计算时候提供额外的信息
ExpandedBlockEnd.gif 
/// </summary>

None.gif   public   interface  IContext
ExpandedBlockStart.gifContractedBlock.gif 
dot.gif {
ExpandedBlockEnd.gif }

None.gif



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

[代码5]

None.gif    public   class  XmlContext : IContext
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   Expression m_Expr;
InBlock.gif   
private XmlNode m_Node;
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif   
/**//// <summary>
InBlock.gif   
/// 当前的节点
ExpandedSubBlockEnd.gif   
/// </summary>

ExpandedSubBlockStart.gifContractedSubBlock.gif   public XmlNode Node dot.gifget dot.gifreturn m_Node; } set dot.gif{ m_Node = value; } }
InBlock.gif
ExpandedSubBlockStart.gifContractedSubBlock.gif   
public XmlContext(Expression expr)dot.gif{m_Expr = expr;}
InBlock.gif
InBlock.gif   
public IOperation UnknownXmlNode(string operation, object[] parameters)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    
return m_Expr.OnUnknownXmlNode(m_Node, operation, this, parameters);
ExpandedSubBlockEnd.gif   }

InBlock.gif
InBlock.gif   
public DataElement UnknownOperation(string operation, object[] parameters)
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    
return m_Expr.OnUnknownOperation(operation, this, parameters);
ExpandedSubBlockEnd.gif   }

ExpandedBlockEnd.gif  }

None.gif
None.gif


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

None.gif    public   object  Extension( string  operation, IContext context,  params   object [] parameters)
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   
if( context is Expression.XmlContext )
ExpandedSubBlockStart.gifContractedSubBlock.gif   
dot.gif{
InBlock.gif    Expression.XmlContext xc 
= (Expression.XmlContext)context;
InBlock.gif    
return xc.UnknownOperation(operation, parameters);
ExpandedSubBlockEnd.gif   }

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

None.gif
None.gif



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

[代码7]

ExpandedBlockStart.gif ContractedBlock.gif   /**/ /// <summary>
InBlock.gif 
/// ICalculator 的摘要说明。
InBlock.gif 
/// 负责把表达式变成实际结果的抽象接口
ExpandedBlockEnd.gif 
/// </summary>

None.gif   public   interface  ICalculator
ExpandedBlockStart.gifContractedBlock.gif 
dot.gif {
InBlock.gif  
object Add(object left, object right, IContext context);
InBlock.gif  dot.gif
InBlock.gif  
object Mod(object left, object right, IContext context);
InBlock.gif
InBlock.gif  
object Eq(object left, object right, IContext context);
InBlock.gif  dot.gif
InBlock.gif  
object LessEq(object left, object right, IContext context);
InBlock.gif
InBlock.gif  
object And(object left, object right, IContext context);
InBlock.gif  
object Or(object left, object right, IContext context);
InBlock.gif
InBlock.gif  
object BitAnd(object left, object right, IContext context);
InBlock.gif  
object BitOr(object left, object right, IContext context);
InBlock.gif  
object BitNot(object val, IContext context);
InBlock.gif
InBlock.gif  
object Plus(object val, IContext context);
InBlock.gif  
object Neg(object val, IContext context);
InBlock.gif
InBlock.gif  
object Not(object val, IContext context);
InBlock.gif
InBlock.gif  
object BoolValue(bool val, IContext context);  
InBlock.gif  dot.gif
InBlock.gif  
object DateTimeValue(DateTime val, IContext context);
InBlock.gif         
object MemberRefValue(Member val, IContext context);
InBlock.gif         
object MemberRefValue(LooseMember val, IContext context);
InBlock.gif
InBlock.gif  
object Extension(string operation, IContext context, params object[] parameters);
ExpandedBlockEnd.gif     }

None.gif
None.gif



熟悉设计模式的大虾立马就可以看出来了,典型的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]

None.gif   public   class  BetweenExpression
None.gif  : TripleExpression
ExpandedBlockStart.gifContractedBlock.gif 
dot.gif {
InBlock.gif  dot.gif
InBlock.gif  
InBlock.gif  
override public object Evaluate( ICalculator cal, IContext context )
ExpandedSubBlockStart.gifContractedSubBlock.gif  
dot.gif{
InBlock.gif   
return cal.Extension( BETWEEN, context
InBlock.gif       ,
base.m_Left.Evaluate(cal, context)
InBlock.gif       ,
base.m_Middle.Evaluate(cal, context)
InBlock.gif       ,
base.m_Right.Evaluate(cal, context), context);
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif }


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

None.gif    public   object  Extension( string  operation, IContext context,  params   object [] parameters)
ExpandedBlockStart.gifContractedBlock.gif  
dot.gif {
InBlock.gif   
if( operation == LikeExpression.LIKE )
InBlock.gif    
return Like(parameters[0], parameters[1], context);
InBlock.gif   
else if( operation == BetweenExpression.BETWEEN )
InBlock.gif    
return Between(parameters[0], parameters[1], parameters[2], context);
InBlock.gif    
InBlock.gif   
else if( context is Expressions.ICallbackContext )
InBlock.gif    
return ((ICallbackContext)context).OnCalculateExtension(operation, parameters);
InBlock.gif
InBlock.gif   
throw new NotSupportedException("不能支持操作:" + operation);
ExpandedBlockEnd.gif  }

None.gif


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

附带源代码到这里下载。

转载于:https://www.cnblogs.com/BigTall/archive/2005/05/28/164183.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值