第十六章 解释器模式 - 自定义语言的实现
一、什么是解释器模式
解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。解释器模式是一种类行为型模式。
在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。
二、文法规则和抽象语法树
解释器模式描述了如何为简单的语言定义一个文法,如何在改语言中表示一个句子,以及如何解释这些句子。
在解释器模式中,我们可以使用一些符号来表示不同的含义,如“|”表示或,“{”与“}”表示组合等等。这些符号即代表着部分的文法规则
除了依靠文法规则来定义一个语言之外,还可以通过抽象语法树的图形方式来直观的表示语言的构成。
例如一个表达式语句 “1+2+3-4+1”,
2. 解释器模式中包含的几个角色:
- AbstractExpression - 抽象表达式
- 在抽象表达式中声明了抽象的解释操作,他是所有终结符表达式和非终结符表达式的公共父类
- TerminalExpression - 终结符表达式
- 是抽象表达式的子类,实现了与文法中的终结符相关联的解释操作
- 在句子中的每一个终结符都是该类的一个实例
- 通常在一个解释器模式中只有少数几个终结符表达式,他们的实例可以通过非终结符表达式组成较为复杂的句子
- NonterminalExpression - 非终结符表达式
- 抽象表达式的子类,实现了文法中非终结符的解释操作
- 由于在非终结符表达式中可以包含终结符表达式,也可以包含非终结符表达式
- 因此其解释操作通常是递归的方式
- Context - 环境类
- 环境类又称为上下文类,用于存储解释器之外的一些全局信息
- 通常临时存储了需要解释的语句
3. 核心类的设计
抽象表达式类
在解释器模式中,每一种终结符和非终结符都有一个具体类与之对应,正因为使用类来表示每一条文法规则,所以系统将具有较好的灵活性和可扩展性。
abstract class AbstractExpression {
public abstract void interpret(Context ctx);
}
终结符表达式
终结符表达式主要是对终结符元素的处理。
class TerminalExpression extends AbstractExpression {
public void interpre(Context ctx) {
// 终结符表达式的解释操作
}
}
非终结符表达式
非终结符表达式将表达式组合成更加复杂的结构,对于包含两个操作元素的非终结符表达式类,其典型代码如下:
class NonterminalExpression extends AbstractExpression {
private AbstractExpression left;
private AbstractExpression right;
public NonterminalExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
public void interpret(Context ctx) {
// 递归调用每一个组成部分的 interpret() 方法
// 在递归调用时指定组成部分的连接方式,即非终结符的功能
}
}
上下文类
环境类Context, 用于存储一些全局信息,通常在Context中包含了一个 HashMap 或者 ArrayList 等类型的集合对象。
class Context {
private HashMap map = new HashMap();
public void assign(String key, String value){
// 往环境中设值
}
public String lookup(String key) {
// 获取存储在环境中的值
}
}
系统可以根据西药来决定是否需要环境类
三、完整实现
// 抽象表达式
abstract class AbstractNode {
public abstract String interpret();
}
// And解释 - 非终结符表达式
class AndNode extends AbstractNode {
private AbstractNode left;
private AbstractNode right;
public AndNode(AbstractNode left, AbstractNode right){
this.left = left;
this.right = right;
}
// And 表达式的解释操作
public String interpret(){
return left.interpret() + "再" + right.interpret();
}
}
// 简单句子解释 - 非终结符表达式
class SentenceNode extends 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 String interpret(){
return direction.interpret() + action.interpret() + distance.interpret();
}
}
// 方向解释 - 终结符表达式
class DirectionNode extends AbstractNode {
private String direction;
public Direction(String direction) {
this.direction = direction;
}
public String interpret() {
switch(direction){
case "up":
return "向上";
case "down":
return "向下";
case "left":
return "向左";
case "right":
return "向右";
default:
return "无效指令"'
}
}
}
// 动作解释 - 终结符表达式
class ActionNode extends AbstractNode {
private String action;
public ActionNode(String action) {
this.action = action;
}
public String interpret() {
switch(action) {
case "move":
return "移动";
case "run":
return "快速移动";
default:
return "无效指令";
}
}
}
// 距离解释 - 终结符表达式
class DistanceNode extends AbstractNode {
private String distance;
public DistanceNode(String distance) {
this.distance = distance;
}
public String interpret() {
return distance;
}
}
// 指令处理类 - 工具类
/**
* 工具类用于对输入指令进行处理,将指令分隔为字符串数组
* 将第一二三个单词组成一个句子,并存入栈中
* 如果发现单词 “and”,则将 “and” 后的第一二三个单词组成一个新的句子作为右表达式,并从栈中取出原先的句子作为左表达式,然后组合成一个And节点存入栈中
* 依次类推,直到整个指令解析结束
*/
class InstructionHandler {
private String instruction;
private AbstractNode node;
public void handle(String instruction) {
AbstractNode left = null;
AbstractNode right = null;
AbstractNode direction = null;
AbstractNode action = null;
AbstractNode distance = null;
// 声明一个栈对象用于存储抽象语法树
Stack stack = new Stack();
// 以空格分隔指令字符串
String[] words = instruction.split(" ");
for(int i = 0;i < words.length; i++){
// 如果遇到 and,则将后三个单词作为三个终结符表达式连成一个简单句子,并作为右表达式
// 从栈顶弹出之前的表达式作为 and 的左表达式,最后将and压入栈中
if(words[i].equalsIgnoreCase("and")){
// 弹出栈顶表达式作为左表达式
left = (AbstractNode) stack.pop();
direction = new DirectionNode(words[++i]);
action = new ActionNode(words[++i]);
distance = new DistanceNode(words[++i]);
// 右表达式
right = new SentenceNode(direction, action, distance);
// 将新表达式压入栈中
stack.push(new AndNode(left, right));
}
// 如果是从头开始解释,则将前三个单词组成一个简单句子并压入栈中
else {
direction = new DirectionNode(words[i]);
action = new ActionNode(words[++i]);
distance = new DistanceNode(words[++i]);
left = new SentenceNode(direction, action, distance);
stack.push(left);
}
}
this.node = (AbstractNode)stack.pop();
}
public String output(){
// 解释表达式
String result = node.interpret();
return result;
}
}
// 客户端 - 测试类
class Client {
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();
System.out.println(outString);
}
}
四、Context的作用
在解释器模式中,环境类Context用于存储解释器之外的一些全局信息,它通常作为参数被传递到所有表达式的解释方法 interpret() 中,可以在 Context 对象中存储访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在 Context 中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。
五、总结
优点
- 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
- 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言
- 实现文法较为容易,在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码
- 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无需修改,符合“开闭原则”
缺点
- 对于复杂文法难以维护,每一条规则至少对应一个类
- 执行效率较低,模式中使用了大量的循环和递归调用,因此在解释时速度很慢
适用场景
- 讲一个需要解释执行的语言中的句子表示为一个丑行语法树
- 一些重复出现的问题可以用一种简单的语言来进行表达
- 一个语言的文法较为简单
- 执行效率不是关键问题
六、练习
Sunny软件公司欲为数据库备份和同步开发一套简单的数据库同步指令,通过指令可以对数据库中的数据和结构进行备份,例如,输入指令“COPY VIEW FROM srcDB TO desDB”表示将数据库srcDB中的所有视图(View)对象都拷贝至数据库desDB;输入指令“MOVE TABLE Student FROM srcDB TO esDB”表示将数据库srcDB中的Student表移动至数据库desDB。试使用解释器模式来设计并实现该数据库同步指令。
import java.util.Stack;
/**
* 解释器模式
* Sunny软件公司欲为数据库备份和同步开发一套简单的数据库同步指令,
* 通过指令可以对数据库中的数据和结构进行备份,
* 例如,
* 输入指令“COPY VIEW FROM srcDB TO desDB”
* 表示将数据库srcDB中的所有视图(View)对象都拷贝至数据库desDB;
* 输入指令“MOVE TABLE Student FROM srcDB TO esDB”
* 表示将数据库srcDB中的Student表移动至数据库desDB。
* 试使用解释器模式来设计并实现该数据库同步指令。
*/
// 抽象指令
abstract class AbstractOrder {
// 抽象公共方法,用于解释具体的指令
abstract String interpret();
}
// 从。。到。。 指令 - 非终结符表达式
class FromOrder extends AbstractOrder {
private AbstractOrder first;
private AbstractOrder second;
public FromOrder(AbstractOrder first, AbstractOrder second) {
this.first = first;
this.second = second;
}
@Override
String interpret() {
return "从" + first.interpret() + "到" + second.interpret();
}
}
// 视图指令 - 终结符表达式
class ViewOrder extends AbstractOrder {
private String view;
public ViewOrder() {
}
public ViewOrder(String view) {
this.view = view;
}
@Override
String interpret() {
if (view == null) {
return "[所有视图]";
}
return "[视图:" + view + "]";
}
}
// 表格指令 - 终结符表达式
class TableOrder extends AbstractOrder {
private String table;
public TableOrder() {
}
public TableOrder(String table) {
this.table = table;
}
@Override
String interpret() {
if (table == null) {
return "[所有表格]";
}
return "[" + table + "表]";
}
}
// 数据库名称指令 - 终结符表达式
class DBOrder extends AbstractOrder {
private String name;
public DBOrder(String name) {
this.name = name;
}
@Override
String interpret() {
return "[数据库:" + name + "]";
}
}
// 备份指令 - 终结符表达式
class CopyOrder extends AbstractOrder {
@Override
String interpret() {
return "拷贝";
}
}
// 移动指令 - 终结符表达式
class MoveOrder extends AbstractOrder {
@Override
String interpret() {
return "移动";
}
}
// 句子解释(Copy/Move FromOder) - 非终结符表达式
class SentenceOrder extends AbstractOrder {
private AbstractOrder direction;
private AbstractOrder action;
private AbstractOrder distance;
public SentenceOrder(AbstractOrder direction, AbstractOrder action, AbstractOrder distance) {
this.direction = direction;
this.action = action;
this.distance = distance;
}
@Override
String interpret() {
return direction.interpret() + action.interpret() + distance.interpret();
}
}
// 指令工具类
class InstructionHandler {
private String instruction; // 输入的指令字符串
private AbstractOrder order; // 字符串转换后的指令
public void handle(String instruction) {
AbstractOrder direction; // sentence 指令的 direction
AbstractOrder action; // sentence 指令的 action
AbstractOrder distance; // sentence 指令的 distance
// 使用栈来保存表达式
Stack<AbstractOrder> stack = new Stack<>();
// 将输入的指令字符串按照 空格 拆分
final String[] words = instruction.split(" ");
for (int i = 0; i < words.length; i++) {
final String word = words[i];
// 如果为 from 指令,则解析 From [DB] TO [DB]
switch (word.toLowerCase()) {
case "from":
String f = words[++i];
++i;
String s = words[++i];
stack.push(new FromOrder(new DBOrder(f), new DBOrder(s)));
break;
case "copy":
stack.push(new CopyOrder());
break;
case "move":
stack.push(new MoveOrder());
break;
case "view":
if (!words[i + 1].equalsIgnoreCase("from"))
stack.push(new ViewOrder(words[++i]));
else
stack.push(new ViewOrder());
break;
case "table":
if (!words[i + 1].equalsIgnoreCase("from"))
stack.push(new TableOrder(words[++i]));
else
stack.push(new TableOrder());
break;
default:
throw new RuntimeException("无效指令");
}
}
distance = stack.pop();
action = stack.pop();
direction = stack.pop();
// order 最终是一个解释类表达式
order = new SentenceOrder(direction, action, distance);
}
public String output() {
return order.interpret();
}
}
public class InterpretPattern {
public static void main(String[] args) {
String order = "MOVE TABLE Student FROM srcDB TO esDB";
final InstructionHandler handler = new InstructionHandler();
handler.handle(order);
final String output = handler.output();
System.out.println(output);
}
}