设计模式(二十)解释器模式

版权声明:转载必须注明本文转自晓_晨的博客:http://blog.csdn.net/niunai112

目录

导航

设计模式之六大设计原则
设计模式(一)单例模式
设计模式(二)工厂模式
设计模式(三)策略模式
设计模式(四)适配器模式
设计模式(五)享元模式
设计模式(六)建造者模式
设计模式(七)原型模式
设计模式(八)桥接模式
设计模式(九)外观模式
设计模式(十)组合模式
设计模式(十一)装饰器模式
设计模式(十二)代理模式
设计模式(十三)迭代器模式
设计模式(十四)观察者模式
设计模式(十五)中介者模式
设计模式(十六)命令模式
设计模式(十七)状态模式
设计模式(十八)访问者模式
设计模式(十九)责任链模式
设计模式(二十)解释器模式
设计模式(二十一)备忘录模式
设计模式(二十二)模板模式
设计模式总结篇(为什么要学习设计模式,学习设计模式的好处)

前言

LZ认为在这23个设计模式中最难的就是解释器模式了,在实际开发过程中也很少会用到这个模式,但LZ这篇文章还是会透彻的理解一遍这个模式,原本LZ是想把解释器模式留到最后一篇来写,但是怕放在最后就草草结束了,所有放到前面来,细细的研究一遍这个模式。
解释器角色:
抽象解释器(AbstractExpression):具体的解释任务由各个实现类完成。
终结符表达式(TerminalExpression):实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结表达式,但有多个实例,对应不同的终结符。
非终结符表达式(NonterminalExpression):文法中的每条规则对应于一个非终结表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
上下文(Context): 上下文环境类,包含解释器之外的全局信息
客户类(Test): 客户端,解析表达式,构建抽象语法树,执行具体的解释操作等.

例子

解释器模式最好的例子还是计算器,LZ用解释器模式来构建一个简单的计算器


/***
 *
 *@Author ChenjunWang
 *@Description:解释器接口
 *@Date: Created in 16:20 2018/4/17
 *@Modified By:
 *
 */
public interface Expression {
    int interpreter(Context context);//一定会有解释方法
}
/***
 *
 *@Author ChenjunWang
 *@Description:抽象非终结符表达式
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public abstract class NonTerminalExpression implements Expression{
    Expression e1,e2;
    public NonTerminalExpression(Expression e1, Expression e2){

        this.e1 = e1;
        this.e2 = e2;
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:减法表达式实现类
 *@Date: Created in 16:57 2018/4/17
 *@Modified By:
 *
 */
public class MinusOperation extends NonTerminalExpression {

    public MinusOperation(Expression e1, Expression e2) {
        super(e1, e2);
    }

 //将两个表达式相减
    @Override
    public int interpreter(Context context) {
        return this.e1.interpreter(context) - this.e2.interpreter(context);
    }
}


/***
 *
 *@Author ChenjunWang
 *@Description:终结符表达式(在这个例子,用来存放数字,或者代表数字的字符)
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public class TerminalExpression implements Expression{

    String variable;
    public TerminalExpression(String variable){

        this.variable = variable;
    }
    @Override
    public int interpreter(Context context) {
        return context.lookup(this);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:
 *@Date: Created in 16:56 2018/4/17
 *@Modified By:
 *
 */
public class PlusOperation extends NonTerminalExpression {

    public PlusOperation(Expression e1, Expression e2) {
        super(e1, e2);
    }

    //将两个表达式相加
    @Override
    public int interpreter(Context context) {
        return this.e1.interpreter(context) + this.e2.interpreter(context);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:上下文类(这里主要用来将变量解析成数字【当然一开始要先定义】)
 *@Date: Created in 16:48 2018/4/17
 *@Modified By:
 *
 */
public class Context {
    private Map<Expression, Integer> map = new HashMap<>();

    //定义变量
    public void add(Expression s, Integer value){
        map.put(s, value);
    }
    //将变量转换成数字
    public int lookup(Expression s){
        return map.get(s);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:测试类
 *@Date: Created in 13:27 2018/4/8
 *@Modified By:
 *
 */
public class Test {
    public static void main(String[] args) {

        Context context = new Context();
        TerminalExpression a = new TerminalExpression("a");
        TerminalExpression b = new TerminalExpression("b");
        TerminalExpression c = new TerminalExpression("c");
        context.add(a, 4);
        context.add(b, 8);
        context.add(c, 2);

        System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
    }
}

运行结果如下
-----------------------------------
10

UML图
interpreteruml
看完后,不知道大家理解了没?不理解是正常的,因为我当初学的时候,也是一脸懵逼的。如果你到这就理解了的话,我想说,你的思维能力真的很强。下面LZ试着帮大家深入理解这个模式,首先是非终结符和,终结符的理解方式,
非终结符表达式(相当于树的树杈):在这个例子中就是相加,相减的表达式,称为非终结符,这是非常形象的,因为当运算遇到这类的表达式的时候,必须先把非终结符的结果计算出来,犹如剥茧一般,一层一层的调用,就比如上面的

new MinusOperation(new PlusOperation(a,b), c).interpreter(context)

这个MinusOperation左边参数是new PlusOperation(a,b),是非终结符表达式,所以要调用PlusOperation,因为PlusOperation的左右两边都是TerminalExpression,是终结符表达式,所以计算然后返回,到最外面的MinusOperation函数,发现右边c是终结符表达式,所以可以计算。

终结符表达式(相当于树的叶子):遇到这个表达式interpreter执行能直接返回结果,不会向下继续调用。

用构建的语法树可以直观的
interpreter
叶子节点即为终结符,树杈即为非终结符,遇到非终结符要继续往下解析,遇到终结符则返回。a+b-c(4+8-2)

看到这如果你已经懂了的话,那就不必再往下看了,下面是拓展,上面的语法树是LZ手动建的(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)),实际情况,客户输入的都是(4+8-2)这样的式子,所以,LZ接下来写的就是解析的输入式子然后自动构建语法树,然后计算结果拉。

/***
 *
 *@Author ChenjunWang
 *@Description:
 *@Date: Created in 16:48 2018/4/17
 *@Modified By:
 *
 */
public class Context {
    private Map<Expression, Integer> map = new HashMap<>();

    public void add(Expression s, Integer value){
        map.put(s, value);
    }
    public Integer lookup(Expression s){
        return map.get(s);
    }
    //构建语法树的主要方法
    public static Expression build(String str) {
        //主要利用栈来实现
        Stack<Expression> objects = new Stack<>();
        for (int i = 0; i < str.length(); i++){
            char c = str.charAt(i);
            //遇到运算符号+号时候
            if (c == '+'){

                //先出栈
                Expression pop = objects.pop();

                //将运算结果入栈
                objects.push(new PlusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));
            } else if (c == '-'){
                //遇到减号类似加号
                Expression pop = objects.pop();

                objects.push(new MinusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));

            } else {
                //遇到非终结符直接入栈(基本就是第一个数字的情况)
                objects.push(new TerminalExpression(String.valueOf(str.charAt(i))));
            }
        }
        //把最后的栈顶元素返回
        return objects.pop();
    }
}
/***
 *
 *@Author ChenjunWang
 *@Description:终结符实现类
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public class TerminalExpression implements Expression {

    String variable;
    public TerminalExpression(String variable){

        this.variable = variable;
    }
    @Override
    public int interpreter(Context context) {
        //因为要兼容之前的版本
        Integer lookup = context.lookup(this);
        if (lookup == null)
            //若在map中能找到对应的数则返回
            return Integer.valueOf(variable);
        //找不到则直接返回(认为输入的就是数字)
        return lookup;
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:测试类
 *@Date: Created in 13:27 2018/4/8
 *@Modified By:
 *
 */
public class Test {
    public static void main(String[] args) {

        Context context = new Context();
        TerminalExpression a = new TerminalExpression("a");
        TerminalExpression b = new TerminalExpression("b");
        TerminalExpression c = new TerminalExpression("c");
        String str = "4+8-2+9+9-8";
        Expression build = Context.build(str);
        System.out.println("4+8-2+9+9-8=" + build.interpreter(context));

        context.add(a, 4);
        context.add(b, 8);
        context.add(c, 2);

        System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
    }
}

运行结果如下
-----------------------------------------
4+8-2+9+9-8=20
10

当然LZ实现的这个计算器不是非常的好,只是为了说明解释器模式,望各位看官见谅哈,若各位有兴趣可以继续完善这个demo,比如增加乘法和除法,对输入的数据进行判断,还有目前对字符串解析的计算肯定只支持(0-9)的数字计算等等,大家可以在这些点上进行拓展。

好了,当你看到这的时候,设计模式的学习已经快到尾声了,与这篇相比,后面的两篇没有太大的难度。各位给自己鼓鼓掌吧!

总结

优点

(1)扩展性强,若要新增乘,除,添加相应的非终结表达式,修改计算逻辑即可。

缺点

(1)需要建大量的类,因为每一种语法都要建一个非终结符的类。
(2)解释的时候采用递归调用方法,导致有时候函数的深度会很深,影响效率。

Git地址

本篇实例Github地址:https://github.com/stackisok/Design-Pattern/tree/master/src/interpreter

回到最上方


有什么不懂或者不对的地方,欢迎留言。
喜欢LZ文章的小伙伴们,可以关注一波,也可以留言,LZ会回你们的。
觉得写得不错的小伙伴,欢迎转载,但请附上原文地址,谢谢^_^!

猜你喜欢

转载自blog.csdn.net/niunai112/article/details/79982712