设计模式-解释器模式
定义:给定一门语言,定义它的文法的一种表示,定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。
使用场景
(1) 当一个语言需要解释执行,并且你可将语言中的句子表示为一个抽象语法树时
(2) 一些重复出现的问题可以用一种简单的语法来表达
(3) 一个简单语法需要解释的场景
看完上面这些, 语言?语句?文法?解释器?
这几个词想必大家都不陌生,编译原理中讲的就是这些,但是概念几乎全忘了。
比如正则表达式,它就是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
一条正则表达式就是一个语言的语句,定义正则表达式的规则就是文法,解释器定义的文法,并且通过解释器可以将这个语句解释为目标对象可以理解的形式。
不管想不想的起来,继续往下看就对了
先来看下解释器模式类图
AbstractExpression 抽象解释器
具体的解释任务由各个实现类完成
TerminalExpression 终结符表达式:具体解释器
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
NonterminalExpression 非终结符表达式,抽象解释器
文法中的每条规则对应一个非终结表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
Context 上下文环境类
包含解释器之外的全局信息
Client 客户类
解析表达式,构建抽象语法树,执行具体的解释操作等
实例如下
下面通过一个“计算器”的实现来详细了解下解释器模式的使用。
本文只是展示解释器模式的使用,故此在这里只讲解 加法、减法 的运算。
代码实现如下
AbstractExpression 抽象解释器
// 抽象、接口表达式
public interface AbstractExpression
{
int Interpreter(Context context);
}
TerminalExpression 终结符表达式:具体解释器
// 终端表达式
public class TerminalExpression : AbstractExpression
{
private string _key;
public TerminalExpression(string key)
{
_key = key;
}
// 解释翻译方法
public int Interpreter(Context context)
{
return context.Get(_key);
}
}
NonterminalExpression 非终结符表达式,抽象解释器
// 抽象非终端表达式
public abstract class NonterminalExpression : AbstractExpression
{
protected AbstractExpression _left;
protected AbstractExpression _right;
public NonterminalExpression(AbstractExpression left, AbstractExpression right)
{
_left = left;
_right = right;
}
// 在抽象类中不实现该方法
public abstract int Interpreter(Context context);
}
非终结符表达式:具体解释器如下
// 减法表达式实现类:具体非终端表达式
public class MinusExpression : NonterminalExpression
{
public MinusExpression(AbstractExpression left, AbstractExpression right) : base(left, right)
{
}
public override int Interpreter(Context context)
{
return _left.Interpreter(context) - _right.Interpreter(context);
}
}
// 假发表达式实现类:具体非终端表达式
public class PlusExpression : NonterminalExpression
{
public PlusExpression(AbstractExpression left, AbstractExpression right) : base(left, right)
{
}
public override int Interpreter(Context context)
{
return _left.Interpreter(context) + _right.Interpreter(context);
}
}
Context 上下文环境类
public class Context
{
private Dictionary<string, int> _dic = new Dictionary<string, int>();
public Context()
{
}
public void Add(string key, int value)
{
_dic[key] = value;
}
public int Get(string key)
{
int value = 0;
_dic.TryGetValue(key, out value);
return value;
}
}
Client 客户类
// 解析表达式,构建抽象语法树,执行具体的解释操作等
public class Calculator
{
private AbstractExpression _expression;
public Calculator(string expressionStr)
{
// string expressionStr = "a+b-c+d";
Stack<AbstractExpression> stack = new Stack<AbstractExpression>();
AbstractExpression left;
AbstractExpression right;
int index = 0;
while (index < expressionStr.Length)
{
switch (expressionStr[index])
{
case '+': //加号
// 从栈中获取左表达式
left = stack.Pop();
// 定义右表达式
string key = Convert.ToString(expressionStr[++index]);
right = new TerminalExpression(key);
// 将 left 和 right 合并为一个新的加法表达式
AbstractExpression plus = new PlusExpression(left, right);
// 将加法表达式压栈
stack.Push(plus);
break;
case '-': // 减号
// 从栈中获取左表达式 同上
left = stack.Pop();
// 定义右表达式
key = Convert.ToString(expressionStr[++index]);
right = new TerminalExpression(key);
// 将 left 和 right 合并为一个新的减法表达式
AbstractExpression minus = new MinusExpression(left, right);
// 将减法表达式压栈
stack.Push(minus);
break;
default:
key = Convert.ToString(expressionStr[index]);
AbstractExpression terminal = new TerminalExpression(key);
stack.Push(terminal);
break;
}
++index;
}
_expression = stack.Pop();
}
public int Calculate(Context context)
{
return _expression.Interpreter(context);
}
}
代码调用如下
public class Client
{
public Client()
{
Function1();
Function2();
}
private void Function1()
{
// 创建上 Context 下文,主要就是用来存储计算公式中每个字母对应的真实数据值
// a+b-c+d
Context context = new Context();
context.Add("a", 10);
context.Add("b", 5);
context.Add("c", 2);
context.Add("d", 8);
// 10-5+2+8 = 21
// a、b、c、d 需要用终端表达式
TerminalExpression aExpression = new TerminalExpression("a");
TerminalExpression bExpression = new TerminalExpression("b");
TerminalExpression cExpression = new TerminalExpression("c");
TerminalExpression dExpression = new TerminalExpression("d");
// a+b 表示为 一个加法表达式传入 加法两端 left(aExpression)、right(bExpression) 两个终端表达式
AbstractExpression aPlusb = new PlusExpression(aExpression, bExpression);
// a+b-c 表示为 一个减法表达式 传入 减法两端 left(上边 a+b 的表达式结果 aPlusb)
// right (cExpression) 终端表达式
AbstractExpression aPlusbMinusC = new MinusExpression(aPlusb, cExpression);
// a+b-c+d 表示为 一个加法表达式 传入 加法两端 left(上边 a+b-c 的表达式结果 aPlusbMinusC)
// right (dExpression) 终端表达式
AbstractExpression aPlusbMinusCPlusD = new PlusExpression(aPlusbMinusC, dExpression);
// 最终调用执行最后得到的 (a+b-c+d aPlusbMinusCPlusD)表达式就能得到计算结果
// 因为 Interpreter 实质上是一个递归调用或者说是深度优先遍历调用,所以会将
// 所有加速到计算的表达式都执行到
int value = aPlusbMinusCPlusD.Interpreter(context);
Console.WriteLine("value:" + value + " \n");
}
private void Function2()
{
// 创建上 Context 下文,主要就是用来存储计算公式中每个字母对应的真实数据值
string expressionStr = "a+b-c+d";
Context context = new Context();
context.Add("a", 10);
context.Add("b", 5);
context.Add("c", 2);
context.Add("d", 8);
Calculator calculator = new Calculator(expressionStr);
int value = calculator.Calculate(context);
Console.WriteLine("value:" + value + " \n");
}
}
测试代码中使用的一个计算公式是:
求 a+b-c+d 的值
数据代入 10-5+2+8 = 21
其中 a、b、c、d 分别对应 10、5、2、8 即 a=10、b=5、c=2、d=8
测试结果如下
测试代码中分别调用了 Function1() 和 Function2()
其中 Function2() 是调用的封装好的计算器类,包含语句的解释和计算,不细说了
看下面 a+b-c+d 构建的语法树
跟节点为 c、d 之间的加法 非终结表达式
从上图可以清晰的感受到 非终结表达式和终结表达式了,
终结表达式相当于叶子节点,调用时不能继续向下调用了
非终结表达式相当于父节点,调用时会继续向下递归、深度优先遍历
按照二叉树的遍历方式来看表达式的最终调用逻辑就是 中序遍历
首先调用 c、d 之间的加法,查看左子树不为空,则先执行左子树 (b、c 之间的减法),然后减法左子树依然不为空,则继续执行减法的左子树(a、b 之间的加法),该加法有两个子节点都是终结表达式,则计算其结果,其结果继续向上 和 c 做减法,然后结果在回到跟节点,和 d 做加法,执行结束,得到结果。
优点:
(1) 扩展性好:可以方便的增加表达式
缺点:
(1) 会引起类膨胀
(2) 解释器模式采用循环和递归调用方法,会导致调用非常复杂,效率是一个不容忽视的问题
(3) 可利用场景较少