21.设计模式--解释器模式(Interperter模式)

2021-11-19 16:15:22 浏览数 (1)

1.定义

解释器模式是一种行为型模式,工作中基本上是用不到的,他的作用就是给定一个语言,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

现在有一个需求,大概就是计算器,首先给定一个公式,然后输入对应的值计算出最终的结果,公式不复杂,只能支持 和-操作。例如:输入a b-c,100、200、100,然后计算结果为200。其中我们输入的a b-c实际上可以理解为是另一种语言,那么此时就需要一个解释器进行语法解释,并计算结果。

现在需求有了我们分析一下公式中有哪些元素,其中有运算符以及参与运算的值,而对于解释器来说实际上只需要解释运算符以及参与运算的值即可,当然运算符可能又存在各种不同的运算符。例如加和减实际上对应的计算规则是不同的。

2.解释器模式结构图

VarExpression 用来解析运算的值,各个公式的运算元素的数量是不同的,每个运 算元素对应了一个 VarExpression 对象,SybmolExpression 是负责运算符号解析的,分别有两个子类 AddExpression(负责加法运算)和 SubExpression(负责减法运算)来实现。

Calculator类用于安排运算的先后顺序(加减法是不用考虑,但是乘除法呢?注意扩展性),并且还要返回结果,因此 我们需要增加一个封装类来处理进行封装,由于我们只作运算,暂时还不与业务有挂钩。

3.解释器模式实现

Expression抽象类,只是定义了一个方法interpreter方法用于进行计算,当然计算的逻辑由子类去实现,而参数HashMap中存放的key就是公式中的参数,例如a、b、c而value则代表的是对用的参数具体的值。

代码语言:javascript复制
public abstract class Expression {
    
    //解析公式和数值,其中var中的key值是是公式中的参数,如a、b、c,value值是具体的数字
    public abstract int interpreter(HashMap<String,Integer> var);
}

VarExpression是Expression的具体实现,用于解析公式中的具体运算值,如a、b、c具体对应的值是多少,当然具体的值就是通过key从HashMap中进行获取的。

代码语言:javascript复制
public class VarExpression extends Expression {

    private String key;

    public VarExpression(String key) {
        this.key = key;
    }

    //从map中取之
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(this.key);
    }
}

SymbolExpression也是Expression的具体实现,用于解析公式运算符,当然对于运算符来说肯定需要知道左边是谁和右边是谁,不然怎么进行计算呢。

代码语言:javascript复制
public abstract class SymbolExpression extends Expression {

    protected Expression left;

    protected Expression right;

    //所有的解析公式都应只关心自己左右两个表达式的结果
    public SymbolExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
}

AddExpression是具体的运算符解析器,是对SymbolExpression的具体实现,对于SymbolExpression来说只需要定义运算的左右两部分,而实际要加还是要减是子类来做的,所以AddExpression就是将左右两部分进行相加。

代码语言:javascript复制
public class AddExpression extends SymbolExpression {

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    //把左右两个表达式运算的结果加起来
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return left.interpreter(var)   right.interpreter(var);
    }
}

而是说SubExpression则是将运算符的左右部分做减法。

代码语言:javascript复制
public class SubExpression extends SymbolExpression {

    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }

    //左右两个表达式相减
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return left.interpreter(var) - right.interpreter(var);
    }
}

Calculator构造函数接受一个表达式,然后把表达式转化为 char 数组, 并判断运算符号,如果是“ ”则调用AddExpression,把左边的数(left 变量)和右边的数(right 变量)放入AddExpression。并且存放到Stack(栈)中。

那左边的数为什么是在堆栈中呢?原因是因为放入栈中是为了让计算有先后顺序,例如a b-c,如果栈中已经有了a b的AddExpression,那么如果再减法的时候应该是(a b)作为左边,而c作为右边放入SubExpression。

代码语言:javascript复制
public class Calculator {
    //定义的表达式
    private Expression expression;

    //构造函数传参,并解析
    public Calculator(String expStr) {
        //定义一个堆栈,安排运算的先后顺序
        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 ' ': //加法
                    //拿到加法的左边值,如a b-c,拿到a
                    left = stack.pop();
                    //拿到加法的右边值,如a b-c,拿到b
                    right = new VarExpression(String.valueOf(charArray[  i]));
                    //将做加法的实体存入栈中
                    stack.push(new AddExpression(left, right));
                    break;
                case '-':
                    //拿到减法的左边值,如a b-c,拿到(a b)
                    left = stack.pop();
                    //拿到减法的右边值,如a b-c,拿到c
                    right = new VarExpression(String.valueOf(charArray[  i]));
                    //将做减法的实体存入栈中
                    stack.push(new SubExpression(left, right));
                    break;
                default: //公式中的变量,即a或b或c
                    stack.push(new VarExpression(String.valueOf(charArray[i])));
            }
        }

        //将需要计算的实体取出来,如a b-c,则拿到(a b)的实体和(a b)-c的实体
        this.expression = stack.pop();
    }

    //开始运算
    public int run(HashMap<String, Integer> var) {
        return this.expression.interpreter(var);
    }
}

Test类来模拟用户情况,用户要求可以扩展,可以修改公式,那就通过接受键盘事件来处理,Client 类的源代码如下。

代码语言:javascript复制
public class Test {
    //运行四则运算
    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));
    }

    //获得表达式
    public static String getExpStr() throws IOException{
        System.out.print("请输入表达式:");
        return (new BufferedReader(new
                InputStreamReader(System.in))).readLine();
    }

    //获得值映射
    public static HashMap<String,Integer> getValue(String exprStr) throws
            IOException {
        HashMap<String, Integer> map = new HashMap<String, Integer>();

        //解析有几个参数要传递
        for (char ch : exprStr.toCharArray()) {
            if (ch != ' ' && ch != '-') {
                if (!map.containsKey(String.valueOf(ch))) { //解决重复参数的问题
                    System.out.print("请输入"   ch   "的值:");
                    String in = (new BufferedReader(new
                            InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }

        return map;
    }
}
代码语言:javascript复制
请输入表达式:a b-c
请输入a的值:100
请输入b的值:200
请输入c的值:20
运算结果为:a b-c=280

解释器模式中的角色

AbstractExpression(抽象解释器),具体的解释任务由各个实现类完成。

concreteExpression(具体的解释器)具体的解释器分为两大类:TerminalExpression:终结符表达式,实现与文法中的元素相关联的解释操作,通常一个解释器模式 中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到我们例子就是 VarExpression 类, 表达式中的每个终结符都在堆栈中产生了一个 VarExpression 对象。 NonterminalExpression(非终结符表达式),文法中的每条规则对应一个非终结表达式,具体到我们的 例子就是加减法规则分别对应到 AddExpression 和 SubExpression 两个类。非终结符表达式根据逻辑的复 杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。 Context(环境角色),具体到我们的例子中是采用 HashMap 代替。

参考文献《设计模式之禅》

代码获取地址:https://gitee.com/bughong/design-pattern

0 人点赞