• 设计模式的征途—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("
    ");
                }
            }
        }

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

    参考资料

      DesignPattern

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

  • 相关阅读:
    超级好用的装机神器——Ventoy
    CentOS7.4安装Nvidia Tesla T4驱动
    ESXI常用命令
    阿里云|腾讯云MySQL备份文件一键恢复工具
    在甲方做三年安全的碎碎念
    golang操作docker
    Nginx Module扩展模块实现
    炒冷饭之ThinkPHP3.2.X RCE漏洞分析
    Windows:sysprep.exe工具:审核模式 VS OOBE模式(工厂模式 VS 用户模式)
    高校毕业生人数增长图
  • 原文地址:https://www.cnblogs.com/edisonchou/p/7512733.html
Copyright © 2020-2023  润新知