虽然目前计算机编程语言有好几百种,但有时人们还是希望用一些简单的语言来实现特定的操作,只需要向计算机输入一个句子或文件,就能按照预定的文法规则来对句子或文件进行解释。例如,我们想要只输入一个加法/减法表达式,它就能够计算出表达式结果。例如输入“1+2+3-4+1”时,将输出计算结果为3。像C++,Java或C#都无法直接解释类似这样的字符串,因此用户必须自定义一套文法规则来实现对这些语句的解释,即设计一个自定义语言。如果所基于的编程语言是面向对象语言,此时可以使用解释器模式实现自定义语言。

解释器模式(Interpreter) 学习难度:★★★★★ 使用频率:★☆☆☆☆

一、格式化指令的需求背景

Background:M公司开发了一套简单的基于字符界面的格式化指令,可以根据输入的指令在字符界面输出一些格式化内容,例如输入“LOOP 2 PRINT 杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉”,将输出以下结果:

其中,关键词LOOP表示循环,后面的数字表示循环次数;PRINT表示打印,后面的字符串表示打印的内容;SPACE表示空格;BREAK表示换行;END表示循环结束。每一个关键词对应一条指令,计算机程序将根据关键词执行相应的处理操作。

  M公司的开发人员分析之后,根据格式化指令中句子的组成,定义了如下文法规则:

expression ::= command*          // 表达式,一个表达式包含多条指令

command ::= loop | primitive           // 语句指令

loop ::= 'loop number' expression 'end'      // 循环指令,其中number为自然数

primitive ::= 'print string' | 'space' | 'byeak'    // 基本指令,其中string为字符串  

二、解释器模式概述

2.1 解释器模式简介

  解释器模式是一种使用频率相对较低但学习难度较大的设计模式,它主要用于描述如何使用面向对象语言构成一个简单的语言解释器。

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

2.2 解释器模式结构

  解释器模式主要包含以下4个角色:

  (1)AbstractExpression(抽象表达式):声明了抽象的解释操作;

  (2)TerminalExpression(终结符表达式):抽象表达式的子类,实现了与文法中的终结符相关联的解释操作,在句中的每一个终结符都是该类的一个实例;

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

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

三、格式化指令的具体实现

3.1 设计结构

  M公司根据文法规则,通过进一步分析,结合解释器模式绘制了如下图所示的结构图:

  其中,Context充当环境类角色,Node充当抽象表达式角色,ExpressionNode、CommandNode和LoopCommandNode充当非终结符表达式角色,PrimitiveCommandNode充当终结符表达式角色。

3.2 代码实现

  (1)环境类:Context

    /// <summary>
/// 环境类:用于存储和操作需要解释的语句,
/// 在本实例中每一个需要解释的单词都可以称为一个动作标记(ActionToker)或命令
/// </summary>
public class Context
{
private int index = -;
private string[] tokens;
private string currentToken; public Context(string text)
{
text = text.Replace(" ", " ");
tokens = text.Split(' ');
NextToken();
} // 获取下一个标记
public string NextToken()
{
if (index < tokens.Length - )
{
currentToken = tokens[++index];
}
else
{
currentToken = null;
} return currentToken;
} // 返回当前的标记
public string GetCurrentToken()
{
return currentToken;
} // 跳过一个标记
public void SkipToken(string token)
{
if (!token.Equals(currentToken, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("错误提示:{0} 解释错误!", currentToken);
} NextToken();
} // 如果当前的标记是一个数字,则返回对应的数值
public int GetCurrentNumber()
{
int number = ;
try
{
// 将字符串转换为整数
number = Convert.ToInt32(currentToken);
}
catch (Exception ex)
{
Console.WriteLine("错误提示:{0}", ex.Message);
} return number;
}
}

  (2)抽象表达式:Node

    /// <summary>
/// 抽象表达式:抽象节点类
/// </summary>
public abstract class Node
{
// 声明一个方法用于解释语句
public abstract void Interpret(Context context);
// 声明一个方法用于执行标记对应的命令
public abstract void Execute();
}

  (3)非终结符表达式:ExpressionNode、CommandNode和LoopCommandNode

    /// <summary>
/// 非终结符表达式:表达式节点类
/// </summary>
public class ExpressionNode : Node
{
// 用于存储多条命令的集合
private IList<Node> nodeList = new List<Node>(); public override void Interpret(Context context)
{
// 循环处理Context中的标记
while (true)
{
// 如果已经没有任何标记,则退出解释
if (context.GetCurrentToken() == null)
{
break;
}
// 如果标记为END,则不解释END并结束本次解释过程,可以继续之后的解释
else if (context.GetCurrentToken().Equals("END", StringComparison.OrdinalIgnoreCase))
{
context.SkipToken("END");
break;
}
// 如果为其它标记,则解释标记并加入命令集合
else
{
Node node = new CommandNode();
node.Interpret(context);
nodeList.Add(node);
}
}
} // 循环执行命令集合中的每一条指令
public override void Execute()
{
foreach (var node in nodeList)
{
node.Execute();
}
}
} /// <summary>
/// 非终结符表达式:语句命令节点类
/// </summary>
public class CommandNode : Node
{
private Node node; public override void Interpret(Context context)
{
// 处理LOOP指令
if (context.GetCurrentToken().Equals("LOOP", StringComparison.OrdinalIgnoreCase))
{
node = new LoopCommand();
node.Interpret(context);
}
// 处理其他指令
else
{
node = new PrimitiveCommand();
node.Interpret(context);
}
} public override void Execute()
{
node.Execute();
}
} /// <summary>
/// 非终结符表达式:循环命令类
/// </summary>
public class LoopCommand : Node
{
// 循环次数
private int number;
// 循环语句中的表达式
private Node commandNode; public override void Interpret(Context context)
{
context.SkipToken("LOOP");
number = context.GetCurrentNumber();
context.NextToken();
// 循环语句中的表达式
commandNode = new ExpressionNode();
commandNode.Interpret(context);
} public override void Execute()
{
for (int i = ; i < number; i++)
{
commandNode.Execute();
}
}
}

  (4)终结符表达式:PrimitiveCommandNode

    /// <summary>
/// 终结符表达式:基本命令类
/// </summary>
public class PrimitiveCommand : Node
{
private string name;
private string text; public override void Interpret(Context context)
{
name = context.GetCurrentToken();
context.SkipToken(name); if (!name.Equals("PRINT", StringComparison.OrdinalIgnoreCase)
&& !name.Equals("BREAK", StringComparison.OrdinalIgnoreCase)
&& !name.Equals("SPACE", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine("非法命令!");
} if (name.Equals("PRINT", StringComparison.OrdinalIgnoreCase))
{
text = context.GetCurrentToken();
context.NextToken();
}
} public override void Execute()
{
if (name.Equals("PRINT", StringComparison.OrdinalIgnoreCase))
{
Console.Write(text);
}
else if (name.Equals("SPACE", StringComparison.OrdinalIgnoreCase))
{
Console.Write(" ");
}
else if (name.Equals("BREAK", StringComparison.OrdinalIgnoreCase))
{
Console.Write("\r\n");
}
}
}

  (5)客户端测试:

    public class Program
{
public static void Main(string[] args)
{
string instruction = "LOOP 2 PRINT 杨过 SPACE SPACE PRINT 小龙女 BREAK END PRINT 郭靖 SPACE SPACE PRINT 黄蓉";
Context context = new Context(instruction); Node node = new ExpressionNode();
node.Interpret(context); Console.WriteLine("源指令 : {0}", instruction);
Console.WriteLine("解释后 : "); node.Execute(); Console.ReadKey();
}
}

  编译调试后运行结果如下图所示:

  

四、解释器模式小结

4.1 主要优点

  (1)易于改变和扩展文法 => 通过继承来改变或扩展

  (2)增加新的解释表达式较为方便 => 只需对应新增一个新的终结符或非终结符表达式,原有代码无须修改,符合开闭原则!

4.2 主要缺点

  (1)对于复杂文法难以维护 => 一条规则一个类,如果太多文法规则,类的个数会剧增!

  (2)执行效率较低 => 使用了大量循环和递归,在解释复杂句子时速度很慢!

4.3 应用场景

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

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

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

  (4)执行效率不是关键问题 => 高效的解释器通常不是通过直接解释抽象语法树来实现的

参考资料

  

  (1)刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—23.解释器(Interpreter)模式的更多相关文章

  1. python 设计模式之解释器(Interpreter)模式

    #写在前面 关于解释器模式,我在网上转了两三圈,心中有了那么一点概念 ,也不知道自己理解的是对还是错. 其实关于每一种设计模式,我总想找出一个答案,那就是为什么要用这种设计模式, 如果不用会怎么样,会 ...

  2. 设计模式(二十三)Interpreter模式

    在Interpreter模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来,即用“迷你语言”编写的“迷你程序”把具体的问题表述出来.迷你程序是无法单独工作的,还需要用java语言编写一个负责 ...

  3. 设计模式C++描述----21.解释器(Iterpreter)模式

    一. 解释器模式 定义:给定一个语言,定义它的文法的一种表示,并定一个解释器,这个解释器使用该表示来解释语言中的句子. 结构如下: 代码如下: //包含解释器之外的一些全局信息 class Conte ...

  4. 设计模式的征途(C#实现)—文章目录索引

    1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的 ...

  5. C#设计模式之二十三解释器模式(Interpreter Pattern)【行为型】

    一.引言   今天我们开始讲"行为型"设计模式的第十一个模式,也是面向对象设计模式的最后一个模式,先要说明一下,其实这个模式不是最后一个模式(按Gof的排序来讲),为什么把它放在最 ...

  6. C#设计模式学习笔记:(23)解释器模式

    本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/8242238.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第十一个模式-- ...

  7. Java设计模式(17)解释器模式(Interpreter模式)

    Interpreter定义:定义语言的文法,并且建立一个解释器来解释该语言中的句子. Interpreter似乎使用面不是很广,它描述了一个语言解释器是如何构成的,在实际应用中,我们可能很少去构造一个 ...

  8. [设计模式]解释器(Interpreter)之大胆向MM示爱吧

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 “我刚写了个小程序,需要你来参与下.”我把MM叫到我的电脑旁,“来把下面这条命令打进去,这是个练习打(Pian)符(ni)号(de)的 ...

  9. 设计模式的征途—16.访问者(Visitor)模式

    在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量计算总价,而药房工作人员根据药品名称和数量准备药品,如下图所示. 在软件开发中,有时候也需 ...

随机推荐

  1. 自动化selenium开发

    一.开发环境搭建 1.Firefox浏览器 1.1 下载firefix并安装. 1.2 Firefox中打开"开始菜单“ -> ”开发者“ -> ”获取更多工具“ -> 搜 ...

  2. 用html+css+js做打地鼠小游戏

    html 代码 first.html <!DOCTYPE html> <html lang="en"> <head> <meta char ...

  3. java自动化测试-http请求get

    首先我10.1过来自己玩通了讨鬼转极,看了电视剧白夜追凶,换了工作小组,这段时间确实比较少的更新博客,确实有点不勤奋,我先自我检讨 我就不赘述java的安装了,这个是比较简单的,有必要的话以后在讲 对 ...

  4. hibernate利用mysql的自增张id属性实现自增长id和手动赋值id并存

    我们知道在mysql中如果设置了表id为自增长属性的话,insert语句中如果对id赋值(值没有被用到过)了,则插入的数据的id会为用户设置的值,并且该表的id的最大值会重新计算,以插入后表的id最大 ...

  5. zabbix自动截图留档_python版

    1 背景     每个DB Server都有zabbix监控,除了异常情况的报警信息外,也会在日检.周检.月检等工作中用到zabbix的监控数据,对zabbix监控数据会做两种处理:1 数据分析(环比 ...

  6. 同步IO和异步IO

    链接: 同步IO和异步IO socket阻塞与非阻塞,同步与异步.I/O模型 Linux的IO系统常用系统调用及分析 linux异步IO的两种方式

  7. Linux下简单C语言小程序的反汇编分析

    韩洋原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在开始,本文为因为参加MOO ...

  8. 为什么国外的 App 很少会有开屏广告?

    前言: 笔者在知乎看到这个问题,觉得这的确是一个值得关注和回答的现象,遂写了回答并整理成本文发布在此抛砖引玉,欢迎讨论. 正文: 古话说得好,先问是不是,再问为什么. 对于「国外的 App 很少有开屏 ...

  9. 面试中常用排序算法实现(Java)

    当我们进行数据处理的时候,往往需要对数据进行查找操作,一个有序的数据集往往能够在高效的查找算法下快速得到结果.所以排序的效率就会显的十分重要,本篇我们将着重的介绍几个常见的排序算法,涉及如下内容: 排 ...

  10. 使用prettytable美化python的print输出

    经常碰到需要将一些数据用表格形式输出来.自己手动写太麻烦. 用prettytable能很好解决这个问题. ...(未完)