行为型模式——解释器(Interpreter)
问题背景
当需要为某一类问题创建文法并解释执行时,考虑使用解释器。比如要实现一个计算器,根据用户输入的表达式字符串输出表达式的值。在第一个版本中,计算器只实现了四则运算的功能,并且不能使用“(”和“)”,但是在以后的版本中会不断支持新的运算符。
解决方案
根据以往的经验,如果在第一个版本把逻辑硬编码到计算器类里,就会造成扩展困难,当下一个版本支持更多运算符的时候,就需要修改计算器类。为了应对变化,我们把每个基本表达式抽象出来,单独实现解释逻辑。提取一个表达式接口IExpression,它包含了一个解释方法Interpret来解释当前表达式,所有具体表达式都实现这个接口。之后添加一个上下文类Context来记录当前表达式的解释情况,Interpret方法接收一个上下文对象参数来确定要解释的上下文。
一个有实际意义的解释器是非常复杂的,实现起来非常困难。本文旨在介绍解释器这种设计思想,而不是要教读者如何实现一个解释器,因此这里只用一个简单的示例。这个示例实现了十以内整数加减法表达式的解析,程序结构如下:
效果
- 易于扩展文法。
- 可以执行复杂逻辑。
缺陷
解释器只有在遇到复杂逻辑时才会用到,但一个复杂的解释器又非常难以实现和维护。词法分析、语法分析这些过程非常复杂,造成了解释器实现逻辑也非常复杂;不配合反射的接口只能将对象的表示剥离,却不能将对象的创建剥离,因此无法做到表达式类和用户完全解耦。
相关模式
- 复合:复杂解释器的抽象语法树是复合结构的一个实例。
- 享元:可以把某些表达式设计成享元。
- 迭代器:可以用迭代器遍历抽象语法树。
- 访问器:可以将具体表达式的逻辑集中到一个类里。
实现
using System;
using System.Collections.Generic;
using System.IO;
namespace Interpreter
{
class Client
{
public interface IExpression
{
int Interpret(Context context);
}
public class ValueExpression
{
private int exp;
public ValueExpression(int exp)
{
this.exp = exp;
}
public int Interpret(Context context)
{
return Convert.ToInt32(((char) exp).ToString());
}
}
public class AddExpression
{
public int Interpret(Context context)
{
return context.Pop() + new ValueExpression(context.Read()).Interpret(context);
}
}
public class SubExpression
{
public int Interpret(Context context)
{
return context.Pop() - new ValueExpression(context.Read()).Interpret(context);
}
}
public class TerminalExpression
{
public int Interpret(Context context)
{
return context.Pop();
}
}
public class Calculator
{
private Context context = new Context();
public int Calculate(string expression)
{
context.Expression = expression;
int peek;
while ((peek = context.Read()) != -1)
{
switch (peek)
{
case int c when c >= '0' && c <= '9':
context.Push(new ValueExpression(c).Interpret(context));
break;
case '+':
context.Push(new AddExpression().Interpret(context));
break;
case '-':
context.Push(new SubExpression().Interpret(context));
break;
}
}
return new TerminalExpression().Interpret(context);
}
}
public class Context
{
private Stack<int> stack = new Stack<int>();
private StringReader expression;
public void Push(int x)
{
stack.Push(x);
}
public int Pop()
{
return stack.Pop();
}
public int Read()
{
return expression.Read();
}
public string Expression
{
set
{
stack.Clear();
expression = new StringReader(value);
}
}
}
static void Main(string[] args)
{
var calc = new Calculator();
var expression = "1+2+3";
var result = calc.Calculate(expression);
Console.WriteLine($"{expression}={result}");
expression = "1-5+2";
result = calc.Calculate(expression);
Console.WriteLine($"{expression}={result}");
expression = "0-5+0";
result = calc.Calculate(expression);
Console.WriteLine($"{expression}={result}");
expression = "7+8+9";
result = calc.Calculate(expression);
Console.WriteLine($"{expression}={result}");
}
}
}