设计模式的征途—23.解释器(Interpreter)模式

虽然目前计算机编程语言有好几百种,但有时人们还是希望用一些简单的语言来实现特定的操作,只需要向计算机输入一个句子或文件,就能按照预定的文法规则来对句子或文件进行解释。例如,我们想要只输入一个加法/减法表达式,它就能够计算出表达式结果。例如输入“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 = -1;
        private string[] tokens;
        private string currentToken;

        public Context(string text)
        {
            text = text.Replace("  ", " ");
            tokens = text.Split(‘ ‘);
            NextToken();
        }

        // 获取下一个标记
        public string NextToken()
        {
            if (index < tokens.Length - 1)
            {
                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 = 0;
            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 = 0; 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

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

时间: 2024-10-11 07:00:44

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

Java 实现解释器(Interpreter)模式

/** * 声明一个抽象的解释操作 * @author stone * */ public interface Interpreter { public void interpret(Context context); //实际中,可以有个返回的类型,定义解释出的数据对象 } public class XmlSaxInterpreter implements Interpreter { @Override public void interpret(Context context) { Syst

设计模式学习总结(23) 中介者模式

本系列主要记录设计模式的意图是什么,它要解决一个什么问题,什么时候可以使用它:它是怎样解决的,掌握它的结构图,记住它的关键代码:可以想到至少两个它的应用实例,一个生活中的,一个软件中的:这个模式的优缺点是什么,其有哪些使用场景,在使用时要注意什么. 尊重作者,转载请注明晔阳的Bloghttp://blog.csdn.net/hacke2 23.中介者模式 意图:用一个中介对象来封装一系列的对象交互,中介者使各对象不须要显式地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互. 主要解决

设计模式的征途—文章目录索引

1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的征途-03.工厂方法(Factory Method)模式 ④ 设计模式的征途-04.抽象工厂(Abstract Factory)模式 ⑤ 设计模式的征途-05.原型(Prototype)模式 ⑥ 设计模式的征途-06.建造者(Builder)模式 3.结构型模式 ① 设计模式的征途-07.适配器(A

Interpreter 模式详解--设计模式(22)

Interpreter 模式的来源: Interpreter(解释器)模式是一种特殊的设计模式,它建立一个解释器(Interpreter),对于特定的计算机程序设计语言,用来解释预先定义的文法.简单地说,Interpreter模式是一种简单的语法解释器构架.解释器模式属于行为模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. Interpreter 模式作用:     正如其名,此模式大多用来解释一些(自定义的)独特语法,例如某些游戏开发引擎中

Interpreter模式(C++解释器模式)

Interpreter模式提供了一个实现语法解释器的框架,其目的就是使用一个解释器为用户提供一个一门定义语言语法表示的解释器,并且通过这个解释器来解释语言中的句子. Interpreter模式使用类来表示文法规则,因此方便于文法的扩展. 代码如下: #include <iostream> #include <string> using namespace std; class Context { public: Context(){} ~Context(){} }; class A

23种设计模式(15):备忘录模式

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样就能够将该对象恢复到原先保存的状态 类型:行为类 类图: 我们在编程的时候,常常须要保存对象的中间状态,当须要的时候,能够恢复到这个状态.比方,我们使用Eclipse进行编程时,假如编写失误(比如不小心误删除了几行代码),我们希望返回删除前的状态,便能够使用Ctrl+Z来进行返回.这时我们便能够使用备忘录模式来实现. 备忘录模式的结构 发起人:记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢

23种设计模式(9):访问者模式(转)

定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. 类型:行为类模式 类图: 访问者模式可能是行为类模式中最复杂的一种模式了,但是这不能成为我们不去掌握它的理由.我们首先来看一个简单的例子,代码如下: 1 class A { 2 public void method1(){ 3 System.out.println("我是A"); 4 } 5 6 public void method2(B b){ 7 b.showA(this)

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

在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量计算总价,而药房工作人员根据药品名称和数量准备药品,如下图所示. 在软件开发中,有时候也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式.在设计模式中,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,该模式就是访问者模式. 访问者模式(Visitor) 学习

23种设计模式(9):访问者模式

定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作. 类型:行为类模式. 类图: 访问者模式可能是行为类模式中最复杂的一种模式了,但是这不能成为我们不去掌握它的理由.我们首先来看一个简单的例子,代码如下: class A { public void method1(){ System.out.println(“我是A”); } public void method2(B b){ b.showA(this); } } class B { pu