设计模式之禅【解释器模式】

真刀实枪之解释器模式

  • 从模型公式说起
    • 需求,输入一个模型公式,然后输入模型中的参数,运算出结果
    • 设计要求
      1. 公式可以运行时编辑
      2. 高扩展性
      3. 效率可以暂不考虑
  • 分析下这个需求,还是比较简单的,就是有一套模板,然后填入参数,计算出相应的结果
    • 这个需求中的角色主要有两个
      1. 运算元素【终结符号:这个元素除了赋值以外,不需要做其他的任何处理】
      2. 运算法则【非终结符号:编写算法进行处理】
  • 运算元素与运算符号的异同点
    • 共同点:都要被解析
    • 不同点:运算元素有相同的功能,可以归为一类;运算符要被分别处理,用不同类来处理
  • 那就从加减模型开始这次的设计之旅吧
    • 加减模型简单类图
    • 看上面这个类图,有什么问题没有?对,运算有先后顺序,加减法可能用不到,但是考虑到扩展性后,乘除加上后就不一样了。好了,问题有了,那么开始解决问题吧!
    • 总的思路有了,现在把细节完善一下吧
  • 设计基本完成,现在动手实现具体的逻辑吧

    • 代码

      • Expression

        package com.peng.js;
        
        import java.util.HashMap;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public abstract class Expression {
            public abstract int interpreter(HashMap<String, Integer> var);
        }
        
      • VarExpression

        package com.peng.js;
        
        import java.util.HashMap;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public class VarExpression extends Expression {
            private String key;
        
            public VarExpression(String key) {
                super();
                this.key = key;
            }
        
            // 从map中取值
            @Override
            public int interpreter(HashMap<String, Integer> var) {
                return var.get(key);
            }
        
        }
        
      • SymbolExpression

        package com.peng.js;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public abstract class SymbolExpression extends Expression {
            protected Expression left;
            protected Expression right;
        
            public SymbolExpression(Expression left, Expression right) {
                super();
                this.left = left;
                this.right = right;
            }
        
        }
        
      • AddExpression

        package com.peng.js;
        
        import java.util.HashMap;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public class AddExpression extends SymbolExpression {
        
            public AddExpression(Expression left, Expression right) {
                super(left, right);
            }
        
            // 把左右两边相加
            @Override
            public int interpreter(HashMap<String, Integer> var) {
                return super.left.interpreter(var) + super.right.interpreter(var);
            }
        
        }
        
      • SubExpression

        package com.peng.js;
        
        import java.util.HashMap;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public class SubExpression extends SymbolExpression {
        
            public SubExpression(Expression left, Expression right) {
                super(left, right);
            }
        
            // 把左右两边相加
            @Override
            public int interpreter(HashMap<String, Integer> var) {
                return super.left.interpreter(var) - super.right.interpreter(var);
            }
        
        }
        
      • Calculator

        package com.peng.js;
        
        import java.util.HashMap;
        import java.util.Stack;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public class Calculator {
            // 定义表达式
            private Expression expression;
        
            // 构造函数传参,并解析
            public Calculator(String expStr) {
                super();
                // 定义一个栈,安排运算的先后顺序
                Stack<Expression> stack = new Stack<Expression>();
                // 表达式拆分为字符数组
                char[] charArray = expStr.toCharArray();
                // 运算
                Expression left = null;
                Expression right = null;
                for (int i = 0; i < charArray.length; i++) {
                    switch (charArray[i]) {
                    case '+': {// 加法
                        // 加法结果放到栈中
                        left = stack.pop();
                        right = new VarExpression(String.valueOf(charArray[++i]));
                        stack.push(new AddExpression(left, right));
                        break;
                    }
                    case '-': {// 减法
                        // 减法结果放到栈中
                        left = stack.pop();
                        right = new VarExpression(String.valueOf(charArray[++i]));
                        stack.push(new SubExpression(left, right));
                        break;
                    }
                    default: {
                        // 公式中的变量
                        stack.push(new VarExpression(String.valueOf(charArray[i])));
                    }
                    }
                }
                this.expression = stack.pop();
            }
        
            // 开始运算
            public int run(HashMap<String, Integer> var) {
                return this.expression.interpreter(var);
            }
        
        }
        
      • Client

        package com.peng.js;
        
        import java.io.BufferedReader;
        import java.io.IOException;
        import java.io.InputStreamReader;
        import java.util.HashMap;
        
        /**
         * @author kungfu~peng
         * @data 2017年12月3日
         * @description
         */
        public class Client {
            public static void main(String[] args) throws IOException {
                String expStr = getExpStr();
                // 赋值
                HashMap<String, Integer> var = getValue(expStr);
                Calculator cal = new Calculator(expStr);
                // 运算结果
                System.out.println("表达式:" + expStr + "的值=" + cal.run(var));
            }
        
            private static HashMap<String, Integer> getValue(String expStr)
                    throws IOException {
                HashMap<String, Integer> map = new HashMap<String, Integer>();
                // 解析有几个参数要传递
                for (char ch : expStr.toCharArray()) {
                    if (ch != '+' && ch != '-') {
                        // 解决重复参数的问题
                        if (!map.containsKey(String.valueOf(ch))) {
                            System.out.println("请输入" + String.valueOf(ch) + "参数的值:");
                            String in = new BufferedReader(new InputStreamReader(
                                    System.in)).readLine();
                            map.put(String.valueOf(ch), Integer.valueOf(in));
                        }
                    }
                }
                return map;
            }
        
            // 获得表达式
            private static String getExpStr() throws IOException {
                System.out.println("请输入表达式:");
                return new BufferedReader(new InputStreamReader(System.in)).readLine();
        
            }
        }
        
      • 执行结果

        请输入表达式:
        a+b+c
        请输入a参数的值:
        1
        请输入b参数的值:
        2
        请输入c参数的值:
        3
        表达式:a+b+c的值=6
        

解释器模式的定义

  • Interpreter Pattern
  • Given a language,define a representation for its grammer along with an interpret uses the representation to interpretwntences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子)

解释器模式的通用类图

  • 解释
    • AbstractExpression:抽象解释器
    • TerminalExpression:终结符表达式【eg:参数】
    • NonterminalExpression:非终结符表达式【eg:符号】
    • Context:环境角色

通用代码

  • AbstractExpression

    package js2;
    /**
     * @author kungfu~peng
     * @data 2017年12月3日
     * @description
     */
    public abstract class Expression {
        // 解析任务
        public abstract Object interpreter(Context ctx);
    }
    
  • TerminalExpression

    package js2;
    
    /**
     * @author kungfu~peng
     * @data 2017年12月3日
     * @description 终结符表达式
     */
    public class TerminalExpression extends Expression {
    
        // 通常终结符表达式只有一个,但是有多个对象
        @Override
        public Object interpreter(Context ctx) {
            return null;
        }
    
    }
    
  • NonterminalExpression

    package js2;
    
    /**
     * @author kungfu~peng
     * @data 2017年12月3日
     * @description 非终结符表达式
     */
    public class NonterminalExpression extends Expression {
    
        // 每个非终结符表达式都会对其他表达式产生依赖
        public NonterminalExpression(Expression... expression) {
            super();
        }
    
        @Override
        public Object interpreter(Context ctx) {
            // 进行文法处理
            return null;
        }
    
    }
    
  • Context

    package js2;
    
    /**
     * @author kungfu~peng
     * @data 2017年12月3日
     * @description 
     */
    public class Context {
    
    }
    
  • Client

    package js2;
    
    import java.util.Stack;
    
    /**
     * @author kungfu~peng
     * @data 2017年12月3日
     * @description
     */
    public class Client {
        public static void main(String[] args) {
            Context context = new Context();
            // 通常定义一个语法容器,容纳一个具体的表达式--ListArray,LinkedList,Stack等类型
            Stack<Expression> stack = null;
            for (;;) {
                // 进行语法树判断,并产生递归调用
            }
            Expression exp = stack.pop();
            // 具体元素进入场景
            exp.interpreter(context);
    
        }
    }
    

解释器模式的应用

  • 解释器模式的优点
    1. 语法分析工具
    2. 扩展性好
  • 解释器模式的缺点
    1. 引起类的膨胀
    2. 采用递归--调用复杂
    3. 效率问题【循环和递归引起的】

解释器模式的使用场景

  • 重复发生的问题【服务器日志的处理】
  • 一个简单语法需要解释的场景【简单:减少循环和递归(SQL语法分析)】

解释器模式使用的注意事项

  • 尽量不要在重要的模块使用--维护是个大问题
  • 可以代替解释器模式的技术:shell,JRuby,Groovy等脚本语言

最佳实践

  • 实际开发中使用的非常少:考虑效率、性能、维护等问题
  • 在大中型的框架中可以看到--数据分析、报表设计、科学计算工具
  • 站在巨人的肩膀【开源解析工具包】
    1. Expression4J
    2. MESP
    3. JEP

声明

  • 摘自秦小波《设计模式之禅》第2版;
  • 仅供学习,严禁商业用途;
  • 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

乘风御浪云帆之上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值