解释器模式
本篇博客将介绍解释器模式,解释器模式是一个学习难度相对较大,但是使用频率又比较低的设计模式。通常如果你不涉及开发新语言的话,是不会用的这个模式的。虽然很少使用,但是对它进行学习后可以加深面向对象思想的理解,并且掌握编程语言中语法规则解释的原理和过程。
模式分类
行为型设计模式。
模式产生的原因
虽然目前计算机编程语言有很多种,但有时人们还是希望可以使用一些简单的语言来实现一些特定的操作,用户只需要像计算机输入一个句子或者一个文件,计算机就可以按照事先定义好的文法规则对句子或者文件进行翻译,从而实现相应的功能。例如一些游戏中会存在一些玩家可以自定义的宏命令,例如《最终幻想14》中的宏编辑器就是一种翻译器。
文法规则和抽象语法树
在正式学习解释器模式之前,我们需要先明白如何通过面向对象的思想体现一个语言的文法规则以及如何构建一个抽象语法树。
对于文法规则我们可以来举个例子:
假设现在我们想要设计一个计算器解释器,使其可以识别并计算例如“1+5+7-4”这样的字符串。我们就需要为这个解释器定义一个文法规则:
//计算器文法规则
//表达式 定义为 值 或者 运算式
expression ::= value | operation
//运算式 定义为 “表达式 + 表达式” 或者 “表达式 - 表达式”
operation ::= expression '+' expression | expression '-' expression
//值 定义为 一个整数
value ::= an integer
如上面所示,这样就够建起了一个加减法的解释器文法规则,其中"::="是定义为的意思,其左边的语言单位通过右边的语言单位进行说明和定义。语言单位有两种表达式,分别是终结符表达式和非终结符表达式,在上面的例子中value是一个终结符表达式,operation是一个非终结表达式。对于是否是终结符表达式的区分标准为如果这个表达式的组成元素还可以继续分解,则其为非终结符表达式。相反如果一个表达式是最基本的语言单位则其为终结符表达式。
除了可以使用文法规则来定义一个语言外,抽象语法树也可以用来定义一个语言。如图:
这就是对应上面文法规则的抽象语法树,每一个文法规则的语言都可以用一个抽象语法树来表示,在抽象语法树中,终结符表达式对应的是叶子节点,而非终结符表达式对应的是非叶子节点。通过抽象语法树我们可以更加直观的了解一个语言的组成。
模式类图
由上图可知解释器模式主要有4个对象构成:
AbstractExpression(抽象表达式):
在抽象表达式中声明了抽象的接受操作,是所有的具体表达式的公共父类。
TerminalExpression(终结符表达式):
终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,句子中的每一个终结符表达式都是这个类的实例。
NonTerminalExpression(非终结符表达式):
非终结符表达式是抽象表达式的子类,它实现了与文法中的非终结符相关联的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般都是通过递归的方式实现的。
Context(环境类):
此类通常会作为参数传入表达式的解释操作中。
代码实现
例子:某软件公司要开发一套机器人控制程序,在该机器人控制程序中包含一些简单的文本控制指令,每一条指令对应一条表达式,该表达式可以是简单表达式也可以是复合表达式。每一个简单表达式由移动方向(direction),移动方式(action),移动距离(distance)构成三部分组成,其中移动方向包括上(up),下(down),左(left)和右(right),移动方式包括移动(move)和跑(run)。移动距离是一个正整数。两个表达式可以通过(and)复合在一起形成更加复杂的表达式。请使用解释器模式实现:
首先,我们需要先将文法规则和抽象语法树构建出来:
文法规则:
expression ::= direction action distance | Composite
Composite ::= expression 'and' expression
direction ::= 'up' | 'down' | 'left' | 'right'
action ::= 'move' | 'run'
distance ::= an integer
抽象语法树:
抽象表达式:
namespace Interpreter.Interpreter.Example
{
public abstract class BaseExpression
{
public abstract string Interpret();
}
}
距离表达式:
namespace Interpreter.Interpreter.Example
{
public class DistanceExpression : BaseExpression
{
private string distance;
public DistanceExpression(string distance)
{
this.distance = distance;
}
public override string Interpret()
{
return distance;
}
}
}
方向表达式:
namespace Interpreter.Interpreter.Example
{
public class DirectionExpression : BaseExpression
{
private string direction;
public DirectionExpression(string direction)
{
this.direction = direction;
}
public override string Interpret()
{
if (direction.Equals("up"))
{
return "向上";
}
else if (direction.Equals("down"))
{
return "向下";
}
else if (direction.Equals("left"))
{
return "向左";
}
else if (direction.Equals("right"))
{
return "向右";
}
return null;
}
}
}
移动方式表达式:
namespace Interpreter.Interpreter.Example
{
public class ActionExpression : BaseExpression
{
private string action;
public ActionExpression(string action)
{
this.action = action;
}
public override string Interpret()
{
if (action.Equals("walk"))
{
return "行走";
}
else if (action.Equals("run"))
{
return "奔跑";
}
return null;
}
}
}
简单表达式:
namespace Interpreter.Interpreter.Example
{
public class SentenceNode : BaseExpression
{
private BaseExpression action;
private BaseExpression direction;
private BaseExpression distance;
public SentenceNode(BaseExpression action, BaseExpression direction, BaseExpression distance)
{
this.action = action;
this.direction = direction;
this.distance = distance;
}
public override string Interpret()
{
return direction.Interpret() + action.Interpret() + distance.Interpret();
}
}
}
复合表达式:
namespace Interpreter.Interpreter.Example
{
public class AndNode : BaseExpression
{
private BaseExpression leftExpression;
private BaseExpression rightExpression;
public AndNode(BaseExpression leftExpression, BaseExpression rightExpression)
{
this.leftExpression = leftExpression;
this.rightExpression = rightExpression;
}
public override string Interpret()
{
return leftExpression.Interpret() + "再" + rightExpression.Interpret();
}
}
}
解释器:
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.Design;
namespace Interpreter.Interpreter.Example
{
public class Interpreter
{
private BaseExpression node;
public void Handle(string expression)
{
BaseExpression
left = null,
right = null,
action = null,
direction = null,
distance = null;
Stack<BaseExpression> stack = new Stack<BaseExpression>();
string[] words = expression.Split(' ');
//通过栈压出一个树结构
for (int i = 0; i < words.Length; i++)
{
if (words[i].Equals("and"))
{
left = stack.Pop();
string word1 = words[++i];
string word2 = words[++i];
string word3 = words[++i];
action = new ActionExpression(word1);
direction = new DirectionExpression(word2);
distance = new DistanceExpression(word3);
right = new SentenceNode(action, direction, distance);
stack.Push(new AndNode(left,right));
}
else
{
string word1 = words[i];
string word2 = words[++i];
string word3 = words[++i];
action = new ActionExpression(word1);
direction = new DirectionExpression(word2);
distance = new DistanceExpression(word3);
left = new SentenceNode(action, direction, distance);
stack.Push(left);
}
}
node = stack.Pop();
}
public void OutPut(out string outPut)
{
outPut = node.Interpret();
}
}
}
Program类:
using System;
using System.Diagnostics;
using Interpreter.Interpreter.Question3;
namespace Interpreter
{
internal class Program
{
public static void Main(string[] args)
{
/*
* Example文法规则构建
* expression : direction | action | distance | composite
* direction : "up" | "down" | "left" | "right"
* action : walk | run
* distance : an integer
* composite : expression "and" expression
*/
string expression = "run up 10 and walk down 20 and run left 30";
Interpreter.Example.Interpreter interpreter = new Interpreter.Example.Interpreter();
interpreter.Handle(expression);
string outPut;
interpreter.OutPut(out outPut);
Console.WriteLine(outPut);
}
}
}
解释器模式总结
解释器模式的优点:
- 解释器模式易于改变和拓展文法。由于在解释器模式中使用类表示语言的文法规则,因此可以通过继承等机制来改变和拓展文法。
- 在解释器模式中,每一条文法规则都可以表示为一个类,因此可以方便的实现一个简单的语言。
- 增加新的解释表达式比较方便。
解释器模式的缺点:
- 执行效率低,解释器模式使用递归和循环来解析文法规则。
- 解释器对于复杂文法规则的语言难以维护。