解释器模式 (Interpreter Pattern)

1. 概述

  定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。

2. 模式中的角色

  2.1 AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。

       2.2 TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。

       2.3 NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。

       2.4 Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。

3. 模式解读

  3.1 模式的类图

C#设计模式读书笔记之解释器模式 (Interpreter Pattern)_Interpreter Pattern

       3.2 代码实现

       本实例对机器人控制指令的输出结果进行模拟,将英文指令翻译为中文指令,实际情况是调用不同的控制程序进行机器人的控制,包括对移动方向、方式和距离的控制等

using System;
using System.Collections;

namespace ConsoleApp2
{
class Class31
{
public static void Main(string[] args)
{
string instruction = "up move 5 and down run 10 and left move 5";
InstructionHandler handler = new InstructionHandler();
handler.Handle(instruction);
string outString;
outString = handler.Output();
Console.WriteLine(outString);

Console.ReadLine();
}
}

//抽象表达式
public abstract class AbstractNode
{
public abstract string Interpret();
}

//And解释:非终结符表达式
public class AndNode : AbstractNode
{

private AbstractNode left; //And的左表达式
private AbstractNode right; //And的右表达式

public AndNode(AbstractNode left, AbstractNode right)
{
this.left = left;
this.right = right;
}

//And表达式解释操作
public override string Interpret()
{
return left.Interpret() + ", 再" + right.Interpret();
}
}

//简单句子解释:非终结符表达式
public class SentenceNode : AbstractNode
{
private AbstractNode direction;
private AbstractNode action;
private AbstractNode distance;

public SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance)
{
this.direction = direction;
this.action = action;
this.distance = distance;
}

//简单句子的解释操作
public override string Interpret()
{
return direction.Interpret() + action.Interpret() + distance.Interpret();
}
}

//方向解释:终结符表达式
public class DirectionNode : AbstractNode
{
private string direction;

public DirectionNode(string direction)
{
this.direction = direction;
}

//方向表达式的解释操作
public override string Interpret()
{
if (direction.Equals("up", StringComparison.CurrentCultureIgnoreCase))
{
return "向上";
}
else if (direction.Equals("down", StringComparison.CurrentCultureIgnoreCase))
{
return "向下";
}
else if (direction.Equals("left", StringComparison.CurrentCultureIgnoreCase))
{
return "向左";
}
else if (direction.Equals("right", StringComparison.CurrentCultureIgnoreCase))
{
return "向右";
}
else
{
return "无效指令";
}
}
}

//动作解释:终结符表达式
public class ActionNode : AbstractNode
{
private string action;

public ActionNode(string action)
{
this.action = action;
}

//动作(移动方式)表达式的解释操作
public override string Interpret()
{
if (action.Equals("move", StringComparison.CurrentCultureIgnoreCase))
{
return "移动";
}
else if (action.Equals("run", StringComparison.CurrentCultureIgnoreCase))
{
return "快速移动";
}
else
{
return "无效指令";
}
}
}

//距离解释:终结符表达式
public class DistanceNode : AbstractNode
{

private string distance;

public DistanceNode(string distance)
{
this.distance = distance;
}

//距离表达式的解释操作
public override string Interpret()
{
return this.distance;
}
}

//指令处理类:工具类
public class InstructionHandler
{
private AbstractNode node;

public void Handle(string instruction)
{
AbstractNode left = null, right = null;
AbstractNode direction = null, action = null, distance = null;
Stack stack = new Stack(); //声明一个栈对象用于存储抽象语法树
string[] words = instruction.Split(' '); //以空格分隔指令字符串
for (int i = 0; i < words.Length; i++)
{
// 本实例采用栈的方式来处理指令,如果遇到“and”,
// 则将其后的三个单词作为三个终结符表达式连成一个简单句子SentenceNode,
// 作为“and”的右表达式,而将从栈顶弹出的表达式作为“and”的左表达式,
// 最后将新的“and”表达式压入栈中。
if (words[i].Equals("and", StringComparison.CurrentCultureIgnoreCase))
{
left = (AbstractNode)stack.Pop(); //弹出栈顶表达式作为左表达式
string word1 = words[++i];
direction = new DirectionNode(word1);
string word2 = words[++i];
action = new ActionNode(word2);
string word3 = words[++i];
distance = new DistanceNode(word3);
right = new SentenceNode(direction, action, distance); //右表达式
stack.Push(new AndNode(left, right)); //将新表达式压入栈中
}
//如果是从头开始进行解释,则将前三个单词组成一个简单句子SentenceNode并将该句子压入栈中
else
{
string word1 = words[i];
direction = new DirectionNode(word1);
string word2 = words[++i];
action = new ActionNode(word2);
string word3 = words[++i];
distance = new DistanceNode(word3);
left = new SentenceNode(direction, action, distance);
stack.Push(left); //将新表达式压入栈中
}
}
this.node = (AbstractNode)stack.Pop(); //将全部表达式从栈中弹出
}

public string Output()
{
string result = node.Interpret(); //解释表达式
return result;
}

}
}

输出结果:

C#设计模式读书笔记之解释器模式 (Interpreter Pattern)_Interpreter Pattern_02

4、模式的优缺点

4.1 优点:

      (1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。

      (2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。

      (3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。

      (4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。

4.2 缺点:

      (1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。

      (2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。

 

5、模式适用场景

      (1) 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。

      (2) 一些重复出现的问题可以用一种简单的语言来进行表达。

      (3) 一个语言的文法较为简单。

      (4) 执行效率不是关键问题。【注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。】