This article is excerpted from This is How Design Patterns should Be Learned

Use interpreter mode to parse mathematical expressions

The following uses interpreter mode to implement a mathematical expression calculator, including addition, subtraction, multiplication, division operations. First define the abstract expression role IArithmeticInterpreter interface.


public interface IArithmeticInterpreter {
    int interpret(a);
}

Copy the code

Create the Interpreter abstract class for the terminating expression role.


public abstract class Interpreter implements IArithmeticInterpreter {

    protected IArithmeticInterpreter left;
    protected IArithmeticInterpreter right;

    public Interpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        this.left = left;
        this.right = right; }}Copy the code

Then create the non-terminal expression role add, subtract, multiply, and divide interpreters, respectively. The code for the addition expression AddInterpreter class looks like this.


public class AddInterpreter extends Interpreter {

    public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        super(left, right);
    }

    public int interpret(a) {
        return this.left.interpret() + this.right.interpret(); }}Copy the code

The SubInterpreter class is coded as follows.


public class SubInterpreter extends Interpreter {
    public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
        super(left, right);
    }

    public int interpret(a) {
        return this.left.interpret() - this.right.interpret(); }}Copy the code

The multiplication expression MultiInterpreter class is coded as follows.


public class MultiInterpreter extends Interpreter {

    public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
        super(left,right);
    }

    public int interpret(a) {
        return this.left.interpret() * this.right.interpret(); }}Copy the code

The DivInterpreter class is coded as follows.


public class DivInterpreter extends Interpreter {

    public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
        super(left,right);
    }

    public int interpret(a) {
        return this.left.interpret() / this.right.interpret(); }}Copy the code

The numeric expression NumInterpreter class is coded as follows.


public class NumInterpreter implements IArithmeticInterpreter {
    private int value;

    public NumInterpreter(int value) {
        this.value = value;
    }


    public int interpret(a) {
        return this.value; }}Copy the code

Next, create the calculator GPCalculator class.


public class GPCalculator {
    private Stack<IArithmeticInterpreter> stack = new Stack<IArithmeticInterpreter>();

    public GPCalculator(String expression) {
        this.parse(expression);
    }

    private void parse(String expression) {
        String [] elements = expression.split("");
        IArithmeticInterpreter left,right;

        for (int i = 0; i < elements.length ; i++) {
            String operator = elements[i];
            if(OperatorUtil.ifOperator(operator)){
                left = this.stack.pop();
                right = new NumInterpreter(Integer.valueOf(elements[++i]));
                System.out.println("The stack" + left.interpret() + "And" + right.interpret());
                this.stack.push(OperatorUtil.getInterpreter(left,right,operator));
                System.out.println("Apply operator:" + operator);
            }else {
                NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i]));
                this.stack.push(numInterpreter);
                System.out.println("Push:"+ numInterpreter.interpret()); }}}public int calculate(a) {
        return this.stack.pop().interpret(); }}Copy the code

The code for the utility class OperatorUtil is as follows.


public class OperatorUtil {

    public static boolean isOperator(String symbol) {
        return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*"));
    }

    public static Interpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) {
        if (symbol.equals("+")) {
            return new AddInterpreter(left, right);
        } else if (symbol.equals("-")) {
            return new SubInterpreter(left, right);
        } else if (symbol.equals("*")) {
            return new MultiInterpreter(left, right);
        } else if (symbol.equals("/")) {
            return new DivInterpreter(left, right);
        }
        return null; }}Copy the code

Finally write the client test code.


public static void main(String[] args) {
        System.out.println("result: " + new GPCalculator("10 + 30").calculate());
        System.out.println("result: " + new GPCalculator("10 + 30-20").calculate());
        System.out.println("result: " + new GPCalculator(100 * 2 + 400 * 1 + 66).calculate());
}

Copy the code

The running result is shown in the figure below.

Of course, the simple calculator above does not take into account priority, but works from left to right. In practical operations, multiplication and division are first-order operations, addition and subtraction second-order operations. The first level operation takes precedence. Alternatively, we can manually adjust the precedence of operations by using parentheses. Let’s optimize the code again by creating a new enumerated class.


public enum OperatorEnum {
    LEFT_BRACKET("("),
    RIGHT_BRACKET(")"),
    SUB("-"),
    ADD("+"),
    MULTI("*"),
    DIV("/"),;private String operator;

    public String getOperator(a) {
        return operator;
    }

    OperatorEnum(String operator) {
        this.operator = operator; }}Copy the code

Then modify the OperatorUtil processing logic to set up two stacks.


public class OperatorUtil {

    public static Interpreter getInterpreter(Stack<IArithmeticInterpreter> numStack, Stack<String> operatorStack) {
        IArithmeticInterpreter right = numStack.pop();
        IArithmeticInterpreter left = numStack.pop();
        String symbol = operatorStack.pop();
        System.out.println("Number out of stack:" + right.interpret() + "," + left.interpret() + ", operator off stack :" + symbol);
        if (symbol.equals("+")) {
            return new AddInterpreter(left, right);
        } else if (symbol.equals("-")) {
            return new SubInterpreter(left, right);
        } else if (symbol.equals("*")) {
            return new MultiInterpreter(left, right);
        } else if (symbol.equals("/")) {
            return new DivInterpreter(left, right);
        }
        return null; }}Copy the code

Modify the GPCalculator code.


public class GPCalculator {

    / / digital stack
    private Stack<IArithmeticInterpreter> numStack = new Stack<IArithmeticInterpreter>();
    // the stack operator
    private Stack<String> operatorStack = new Stack<String>();
    /** * parse the expression *@param expression
     */
    public GPCalculator(String expression) {
        this.parse(expression);
    }

    private void parse(String input) {
        // Remove whitespace from an expression
        String expression = this.fromat(input);
        System.out.println("Standard expression: + expression);
        for (String s : expression.split("")) {
            if (s.length() == 0) {// If it is a space, continue the loop and do nothing
                continue;
            }
            // If it is addition or subtraction, since the addition or subtraction has the lowest priority, the operator in the stack will operate whenever it is encountered
            else if (s.equals(OperatorEnum.ADD.getOperator())
                    || s.equals(OperatorEnum.SUB.getOperator())) {
                // When the stack is not empty and the top element of the stack is any addition, subtraction, multiplication, and division
                while(! operatorStack.isEmpty() &&(operatorStack.peek().equals(OperatorEnum.SUB.getOperator()) || operatorStack.peek().equals(OperatorEnum.ADD.getOperator()) || operatorStack.peek().equals(OperatorEnum.MULTI.getOperator()) || operatorStack.peek().equals(OperatorEnum.DIV.getOperator()))) {// The results are stored in the stack
                    numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
                }
                // After the operation, the current operator is pushed onto the stack
                System.out.println("Operator push :"+s);
                operatorStack.push(s);
            }
            // The current operator is multiplication and division because it takes precedence over addition and subtraction
		   // If the uppermost item is multiplied by and divided by, then it is calculated, otherwise it is directly pushed
            else if (s.equals(OperatorEnum.MULTI.getOperator())
                    || s.equals(OperatorEnum.DIV.getOperator())) {
                while(! operatorStack.isEmpty()&&( operatorStack.peek().equals(OperatorEnum.MULTI.getOperator()) || operatorStack.peek().equals(OperatorEnum.DIV.getOperator()))) { numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack)); }// push the current operator onto the stack
                System.out.println("Operator push :"+s);
                operatorStack.push(s);
            }
            The trim() function is used to remove whitespace. The trim() function is used to remove whitespace
            else if (s.equals(OperatorEnum.LEFT_BRACKET.getOperator())) {
                System.out.println("Operator push :"+s);
                operatorStack.push(OperatorEnum.LEFT_BRACKET.getOperator());
            }
            // If the parenthesis is close, the stack operator is cleared until the parenthesis is left
            else if (s.equals(OperatorEnum.RIGHT_BRACKET.getOperator())) {
                while(! OperatorEnum.LEFT_BRACKET.getOperator().equals(operatorStack.peek())) {// start the operation
                    numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
                }
                // Clear the open parenthesis after the operation
                String pop = operatorStack.pop();
                System.out.println("Parenthesis operation completed, clear close parenthesis on stack:"+pop);
            }
            // If it is a number, it goes directly to the data stack
            else {
                // Convert a numeric string to a number and store it on the stack
                NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(s));
                System.out.println("Number push:"+s); numStack.push(numInterpreter); }}// Finally, when the stack is not empty, the operation continues until the stack is empty
        while (!operatorStack.isEmpty()) {
            numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
        }
    }

    /** * computes the result to stack *@return* /
    public int calculate(a) {
        return this.numStack.pop().interpret();
    }

    /** * in standard form, easy to split *@param expression
     * @return* /
    private String fromat(String expression) {
        String result = "";
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == '(' || expression.charAt(i) == ') ' ||
                expression.charAt(i) == '+' || expression.charAt(i) == The '-' ||
                expression.charAt(i) == The '*' || expression.charAt(i) == '/')
                // Add a space between the operator and the number
                result += ("" + expression.charAt(i) + "");
            else
                result += expression.charAt(i);
        }
        returnresult; }}Copy the code

At this point, look at the client-side test code.


public static void main(String[] args) {
        System.out.println("result: " + new GPCalculator("10 + 30 / ((6-4) * 2-2)").calculate());
}

Copy the code

Run to get the expected results, as shown in the figure below.

Application of interpreter pattern in JDK source code

Let’s look at the compilation and parsing of regular expressions by Pattern in the JDK source code.


public final class Pattern implements java.io.Serializable {...private Pattern(String p, int f) {
        pattern = p;
        flags = f;

        if((flags & UNICODE_CHARACTER_CLASS) ! =0)
            flags |= UNICODE_CASE;

     
        capturingGroupCount = 1;
        localCount = 0;

        if (pattern.length() > 0) {
            compile();
        } else {
            root = newStart(lastAccept); matchRoot = lastAccept; }}...public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }
    public static Pattern compile(String regex, int flags) {
        return newPattern(regex, flags); }... }Copy the code

Application of interpreter pattern in Spring source code

Look again at the ExpressionParser interface in Spring.


public interface ExpressionParser {

	Expression parseExpression(String expressionString) throws ParseException;

	Expression parseExpression(String expressionString, ParserContext context) throws ParseException;

}

Copy the code

We won’t go into the source code here, but we can get a rough idea of how it works from the examples we wrote earlier. Write some client-side code to verify this. The client test code is as follows.


    public static void main(String[] args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(100 * 2 + 400 * 1 + 66);
        int result = (Integer) expression.getValue();
        System.out.println("The result is:" + result);
    }
		
Copy the code

The running result is shown in the figure below.

It can be seen from the above figure that the running results are consistent with the expected results.

Pay attention to “Tom play architecture” reply to “design pattern” can obtain the complete source code.

Tom play architecture: 30 real cases of design patterns (attached source code), the challenge of annual salary 60W is not a dream

This article is “Tom play structure” original, reproduced please indicate the source. Technology is to share, I share my happiness! If this article is helpful to you, welcome to follow and like; If you have any suggestions can also leave a comment or private letter, your support is my motivation to adhere to the creation. Pay attention to “Tom bomb architecture” for more technical dry goods!