设计模式-解释器模式

设计模式-解释器模式

定义:给定一门语言,定义它的文法的一种表示,定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。

使用场景
(1) 当一个语言需要解释执行,并且你可将语言中的句子表示为一个抽象语法树时
(2) 一些重复出现的问题可以用一种简单的语法来表达
(3) 一个简单语法需要解释的场景

看完上面这些, 语言?语句?文法?解释器?
这几个词想必大家都不陌生,编译原理中讲的就是这些,但是概念几乎全忘了。

比如正则表达式,它就是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
一条正则表达式就是一个语言的语句,定义正则表达式的规则就是文法,解释器定义的文法,并且通过解释器可以将这个语句解释为目标对象可以理解的形式。

不管想不想的起来,继续往下看就对了

先来看下解释器模式类图
在这里插入图片描述
AbstractExpression 抽象解释器
具体的解释任务由各个实现类完成

TerminalExpression 终结符表达式:具体解释器
实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。

NonterminalExpression 非终结符表达式,抽象解释器
文法中的每条规则对应一个非终结表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。

Context 上下文环境类
包含解释器之外的全局信息

Client 客户类
解析表达式,构建抽象语法树,执行具体的解释操作等

实例如下
下面通过一个“计算器”的实现来详细了解下解释器模式的使用。
本文只是展示解释器模式的使用,故此在这里只讲解 加法、减法 的运算。

代码实现如下
AbstractExpression 抽象解释器

    // 抽象、接口表达式
    public interface AbstractExpression
    {
        int Interpreter(Context context);
    }

TerminalExpression 终结符表达式:具体解释器

    // 终端表达式
    public class TerminalExpression : AbstractExpression
    {
        private string _key;

        public TerminalExpression(string key)
        {
            _key = key;
        }

        // 解释翻译方法
        public int Interpreter(Context context)
        {
            return context.Get(_key);
        }
    }

NonterminalExpression 非终结符表达式,抽象解释器

    // 抽象非终端表达式
    public abstract class NonterminalExpression : AbstractExpression
    {
        protected AbstractExpression _left;
        protected AbstractExpression _right;

        public NonterminalExpression(AbstractExpression left, AbstractExpression right)
        {
            _left = left;
            _right = right;
        }

        // 在抽象类中不实现该方法
        public abstract int Interpreter(Context context);
    }

非终结符表达式:具体解释器如下

    // 减法表达式实现类:具体非终端表达式
    public class MinusExpression : NonterminalExpression
    {
        public MinusExpression(AbstractExpression left, AbstractExpression right) : base(left, right)
        {
        }

        public override int Interpreter(Context context)
        {
            return _left.Interpreter(context) - _right.Interpreter(context);
        }
    }

    // 假发表达式实现类:具体非终端表达式
    public class PlusExpression : NonterminalExpression
    {
        public PlusExpression(AbstractExpression left, AbstractExpression right) : base(left, right)
        {
        }

        public override int Interpreter(Context context)
        {
            return _left.Interpreter(context) + _right.Interpreter(context);
        }
    }

Context 上下文环境类

    public class Context
    {
        private Dictionary<string, int> _dic = new Dictionary<string, int>();
        public Context()
        {
        }

        public void Add(string key, int value)
        {
            _dic[key] = value;
        }

        public int Get(string key)
        {
            int value = 0;
            _dic.TryGetValue(key, out value);
            return value;
        }
    }

Client 客户类

    // 解析表达式,构建抽象语法树,执行具体的解释操作等
    public class Calculator
    {
        private AbstractExpression _expression;
        public Calculator(string expressionStr)
        {
            // string expressionStr = "a+b-c+d";
            Stack<AbstractExpression> stack = new Stack<AbstractExpression>();
            AbstractExpression left;
            AbstractExpression right;

            int index = 0;
            while (index < expressionStr.Length)
            {
                switch (expressionStr[index])
                {
                    case '+':  //加号
                        // 从栈中获取左表达式
                        left = stack.Pop();
                        // 定义右表达式
                        string key = Convert.ToString(expressionStr[++index]);
                        right = new TerminalExpression(key);
                        // 将 left 和 right 合并为一个新的加法表达式
                        AbstractExpression plus = new PlusExpression(left, right);
                        // 将加法表达式压栈
                        stack.Push(plus);
                        break;
                    case '-':  // 减号
                        // 从栈中获取左表达式 同上
                        left = stack.Pop();
                        // 定义右表达式
                        key = Convert.ToString(expressionStr[++index]);
                        right = new TerminalExpression(key);
                        // 将 left 和 right 合并为一个新的减法表达式
                        AbstractExpression minus = new MinusExpression(left, right);
                        // 将减法表达式压栈
                        stack.Push(minus);
                        break;
                    default:
                        key = Convert.ToString(expressionStr[index]);
                        AbstractExpression terminal = new TerminalExpression(key);
                        stack.Push(terminal);
                        break;
                }

                ++index;
            }

            _expression = stack.Pop();
        }

        public int Calculate(Context context)
        {
            return _expression.Interpreter(context);
        }
    }

代码调用如下

    public class Client
    {
        public Client()
        {
            Function1();
            Function2();
        }

        private void Function1()
        {
            // 创建上 Context 下文,主要就是用来存储计算公式中每个字母对应的真实数据值
            // a+b-c+d
            Context context = new Context();
            context.Add("a", 10);
            context.Add("b", 5);
            context.Add("c", 2);
            context.Add("d", 8);
            // 10-5+2+8 = 21

            // a、b、c、d 需要用终端表达式
            TerminalExpression aExpression = new TerminalExpression("a");
            TerminalExpression bExpression = new TerminalExpression("b");
            TerminalExpression cExpression = new TerminalExpression("c");
            TerminalExpression dExpression = new TerminalExpression("d");

            // a+b 表示为 一个加法表达式传入 加法两端 left(aExpression)、right(bExpression) 两个终端表达式
            AbstractExpression aPlusb = new PlusExpression(aExpression, bExpression);

            // a+b-c 表示为 一个减法表达式 传入 减法两端 left(上边 a+b 的表达式结果 aPlusb)
            // right (cExpression) 终端表达式
            AbstractExpression aPlusbMinusC = new MinusExpression(aPlusb, cExpression);

            // a+b-c+d 表示为 一个加法表达式 传入 加法两端 left(上边  a+b-c 的表达式结果 aPlusbMinusC)
            // right (dExpression) 终端表达式
            AbstractExpression aPlusbMinusCPlusD = new PlusExpression(aPlusbMinusC, dExpression);

            // 最终调用执行最后得到的 (a+b-c+d aPlusbMinusCPlusD)表达式就能得到计算结果
            // 因为 Interpreter 实质上是一个递归调用或者说是深度优先遍历调用,所以会将
            // 所有加速到计算的表达式都执行到
            int value = aPlusbMinusCPlusD.Interpreter(context);
            Console.WriteLine("value:" + value + " \n");
        }

        private void Function2()
        {
            // 创建上 Context 下文,主要就是用来存储计算公式中每个字母对应的真实数据值
            string expressionStr = "a+b-c+d";
            Context context = new Context();
            context.Add("a", 10);
            context.Add("b", 5);
            context.Add("c", 2);
            context.Add("d", 8);

            Calculator calculator = new Calculator(expressionStr);
            int value = calculator.Calculate(context);
            Console.WriteLine("value:" + value + " \n");
        }
    }

测试代码中使用的一个计算公式是:
求 a+b-c+d 的值
数据代入 10-5+2+8 = 21
其中 a、b、c、d 分别对应 10、5、2、8 即 a=10、b=5、c=2、d=8
测试结果如下
在这里插入图片描述
测试代码中分别调用了 Function1() 和 Function2()
其中 Function2() 是调用的封装好的计算器类,包含语句的解释和计算,不细说了

看下面 a+b-c+d 构建的语法树
在这里插入图片描述
跟节点为 c、d 之间的加法 非终结表达式
从上图可以清晰的感受到 非终结表达式和终结表达式了,
终结表达式相当于叶子节点,调用时不能继续向下调用了
非终结表达式相当于父节点,调用时会继续向下递归、深度优先遍历

按照二叉树的遍历方式来看表达式的最终调用逻辑就是 中序遍历

首先调用 c、d 之间的加法,查看左子树不为空,则先执行左子树 (b、c 之间的减法),然后减法左子树依然不为空,则继续执行减法的左子树(a、b 之间的加法),该加法有两个子节点都是终结表达式,则计算其结果,其结果继续向上 和 c 做减法,然后结果在回到跟节点,和 d 做加法,执行结束,得到结果。

优点:
(1) 扩展性好:可以方便的增加表达式

缺点:
(1) 会引起类膨胀
(2) 解释器模式采用循环和递归调用方法,会导致调用非常复杂,效率是一个不容忽视的问题
(3) 可利用场景较少

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值