0 讲讲运算的核心——模型公式及其如何实现
0.1 业务需求:输入一个模型公式(加、减运算),然后输入模型中的参数,运算出结果
设计要求
● 公式可以运行时编辑,并且符合正常算术书写方式,例如a b-c ● 高扩展性,未来增加指数、开方、极限、求导等运算符号时较少改动 ● 效率可以不用考虑,晚间批量运算
需求不复杂,若仅仅对数字采用四则运算,每个程序员都可以写出来 但是增加了增加模型公式就复杂了
先解释一下为什么需要公式,而不采用直接计算的方法,例如有如下3个公式 ● 业务种类1的公式:a b c-d ● 业务种类2的公式:a b e-d ● 业务种类3的公式:a-f 其中,a、b、c、d、e、f参数的值都可以取得,如果使用直接计算数值的方法需要为每个品种写一个算法,目前仅仅是3个业务种类,那上百个品种呢?凉透了吧!建立公式,然后通过公式运算才是王道
以实现加减法公式为例,说明如何解析一个固定语法逻辑 采用逐步分析方法,带领大家了解实现过程
想想公式中有什么?运算元素和运算符号
- 运算元素
指a、b、c等符号,需要具体赋值的对象,也叫做
终结符号
,为什么叫终结符号呢? 因为这些元素除了需要赋值外,不需要做任何处理,所有运算元素都对应一个具体的业务参数,这是语法中最小的单元逻辑,不可再分 - 运算符号
就是加减符号,需要我们编写算法进行处理,每个运算符号都要对应处理单元,否则公式无法运行,运算符号也叫做
非终结符号
共同点是都要被解析,不同点是所有运算元素具有相同的功能,可以用一个类表示 而运算符号则是需要分别进行解释,加法需要加法解析器,减法需要减法解析器
初步分析加减法类图
这是一个很简单的类图
VarExpression
用来解析运算元素,各个公式能运算元素的数量是不同的,但每个运算元素都对应一个VarExpression
对象
SybmolExpression
负责解析符号,由两个子类
-
AddExpression
(负责加法运算) -
SubExpression
(负责减法运算) 解析器的开发工作已经完成了,但是需求还没有完全实现。我们还需要对解析器进行封装, 来实现
解析的工作完成了,我们还需要把安排运行的先后顺序(加减法不用考虑,但是乘除法呢?注意扩展性
),并且还要返回结果,因此我们需要增加一个封装类来进行封装处理,由于我们只做运算,暂时还不与业务有关联,定义为Calculator
类
优化后加减法类图
Calculator的作用是封装,根据迪米特法则,Client
只与直接的朋友Calculator
交流,与其他类没关系
完整加减法类图
代码实现
- Expression抽象类
- 变量解析器
- 抽象运算符号解析器
每个运算符号都只和自己左右两个数字有关系,但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression的实现类,于是在对运算符解析的子类中增加了一个构造函数,传递左右两个表达式。
- 加法解析器
- 减法解析器
- Calculator 我们还需要对解析器进行封装
Calculator构造函数接收一个表达式,然后把表达式转化为char数组,并判断运算符号,如果是“ ”则进行加法运算,把左边的数(left变量)和右边的数(right变量)加起来就可以了 那左边的数为什么是在栈中呢?例如这个公式:a b-c,根据for循环,首先被压入栈中的应该是有a元素生成的VarExpression对象,然后判断到加号时,把a元素的对象VarExpression从栈中弹出,与右边的数组b进行相加,b又是怎么得来的呢?当前的数组游标下移一个单元格即可,同时为了防止该元素再次被遍历,则通过 i的方式跳过下一个遍历——于是一个加法的运行结束。减法也采用相同的运行原理。
- 客户模拟类 为了满足业务要求,我们设置了一个Client类来模拟用户情况,用户要求可以扩展,可以修改公式,那就通过接收键盘事件来处理
其中,getExpStr是从键盘事件中获得的表达式,getValue方法是从键盘事件中获得表达式中的元素映射值 ● 首先,要求输入公式。 ● 其次,要求输入公式中的参数。 ● 最后,运行出结果
我们是不是可以修改公式?当然可以,我们只要输入公式,然后输入相应的值就可以了,公式是在运行时定义的,而不是在运行前就制定好的 先公式,然后赋值,运算出结果
需求已经开发完毕,公式可以自由定义,只要符合规则(有变量有运算符合)就可以运算出结果;若需要扩展也非常容易,只要增加BaseSymbolExpression的子类就可以了,这就是解释器模式。
1 定义与类型
- 解释器模式 Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。) 一种按照规定语法进行解析的方案,在现在项目中使用较少
解释器模式通用类图
● AbstractExpression——抽象解释器
具体的解释任务由各个实现类完成
具体的解释器分别由TerminalExpression
和Non-terminalExpression
完成
● TerminalExpression——终结符表达式
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符
具体到我们例子就是VarExpression
类,表达式中的每个终结符都在栈中产生了一个VarExpression对象
● NonterminalExpression——非终结符表达式
文法中的每条规则对应于一个非终结表达式,具体到我们的例子就是加减法规则分别对应到AddExpression和SubExpression两个类。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
● Context——环境角色
具体到我们的例子中是采用HashMap代替。
解释器是一个比较少用的模式,以下为其通用源码,可以作为参考。抽象表达式通常只有一个方法,如代码清单27-8所示。
适用场景
优点
缺点
相关设计模式
适配器模式不需要预先知道要适配的规则 而解释器模式则需要将规则写好,并根据规则进行解释