這個無敵設計,可以解析並運算任意數學表達式


本文節選自《設計模式就該這樣學》

1 使用解釋器模式解析數學表達式

下面用解釋器模式來實現一個數學表達式計算器,包含加、減、乘、除運算。
首先定義抽象表達式角色IArithmeticInterpreter接口。


public interface IArithmeticInterpreter {
    int interpret();
}

創建終結表達式角色Interpreter抽象類。


public abstract class Interpreter implements IArithmeticInterpreter {

    protected IArithmeticInterpreter left;
    protected IArithmeticInterpreter right;

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

然后分別創建非終結符表達式角色加、減、乘、除解釋器,加法運算表達式AddInterpreter類的代碼如下。


public class AddInterpreter extends Interpreter {

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

    public int interpret() {
        return this.left.interpret() + this.right.interpret();
    }
}

減法運算表達式SubInterpreter類的代碼如下。


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

    public int interpret() {
        return this.left.interpret() - this.right.interpret();
    }
}

乘法運算表達式MultiInterpreter類的代碼如下。


public class MultiInterpreter extends Interpreter {

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

    public int interpret() {
        return this.left.interpret() * this.right.interpret();
    }

}

除法運算表達式DivInterpreter類的代碼如下。


public class DivInterpreter extends Interpreter {

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

    public int interpret() {
        return this.left.interpret() / this.right.interpret();
    }

}

數字表達式NumInterpreter類的代碼如下。


public class NumInterpreter implements IArithmeticInterpreter {
    private int value;

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


    public int interpret() {
        return this.value;
    }
}

接着創建計算器GPCalculator類。


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("出棧" + left.interpret() + "和" + right.interpret());
                this.stack.push(OperatorUtil.getInterpreter(left,right,operator));
                System.out.println("應用運算符:" + operator);
            }else {
                NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i]));
                this.stack.push(numInterpreter);
                System.out.println("入棧:" + numInterpreter.interpret());
            }

        }
    }

    public int calculate() {
        return this.stack.pop().interpret();
    }
}

工具類OperatorUtil的代碼如下。


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;
    }
}

最后編寫客戶端測試代碼。


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());
}

運行結果如下圖所示。

file

當然,上面的簡易計算器還沒有考慮優先級,就是從左至右依次運算的。在實際運算中,乘法和除法屬於一級運算,加法和減法屬於二級運算。一級運算需要優先計算。另外,我們可以通過使用括號手動調整運算的優先級。我們再優化一下代碼,首先新建一個枚舉類。


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

    public String getOperator() {
        return operator;
    }

    OperatorEnum(String operator) {
        this.operator = operator;
    }
}

然后修改OperatorUtil的處理邏輯,設置兩個棧。


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("數字出棧:" + right.interpret() + "," + left.interpret() + ",操作符出棧:" + 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;
    }
}

修改GPCalculator的代碼。


public class GPCalculator {

    //數字stack
    private Stack<IArithmeticInterpreter> numStack = new Stack<IArithmeticInterpreter>();
    //操作符stack
    private Stack<String> operatorStack = new Stack<String>();
    /**
     * 解析表達式
     * @param expression
     */
    public GPCalculator(String expression) {
        this.parse(expression);
    }

    private void parse(String input) {
        //對表達式去除空字符操作
        String expression = this.fromat(input);
        System.out.println("標准表達式:" + expression);
        for (String s : expression.split(" ")) {
            if (s.length() == 0){
                //如果是空格,則繼續循環,什么也不操作
                continue;
            }
            //如果是加減,因為加減的優先級最低,所以這里只要遇到加減號,無論操作符棧中是什么運算符都要運算
            else if (s.equals(OperatorEnum.ADD.getOperator())
                    || s.equals(OperatorEnum.SUB.getOperator())) {
                //當棧不是空的,並且棧中最上面的一個元素是加減乘除的任意一個
                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()))) {
                    //結果存入棧中
                    numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
                }
                //運算完后將當前的運算符入棧
                System.out.println("操作符入棧:"+s);
                operatorStack.push(s);
            }
            //當前運算符是乘除的時候,因為優先級高於加減
		   //所以要判斷最上面的是否是乘除,如果是乘除,則運算,否則直接入棧
            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));
                }
                //將當前操作符入棧
                System.out.println("操作符入棧:"+s);
                operatorStack.push(s);
            }
            //如果是左括號,則直接入棧,什么也不用操作,trim()函數是用來去除空格的,由於上面的分割				 操作,可能會令操作符帶有空格
            else if (s.equals(OperatorEnum.LEFT_BRACKET.getOperator())) {
                System.out.println("操作符入棧:"+s);
                operatorStack.push(OperatorEnum.LEFT_BRACKET.getOperator());
            }
            //如果是右括號,則清除棧中的運算符直至左括號
            else if (s.equals(OperatorEnum.RIGHT_BRACKET.getOperator())) {
                while (!OperatorEnum.LEFT_BRACKET.getOperator().equals(operatorStack.peek())) {
                    //開始運算
                    numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
                }
                //運算完之后清除左括號
                String pop = operatorStack.pop();
                System.out.println("括號運算操作完成,清除棧中右括號:"+pop);
            }
            //如果是數字,則直接入數據的棧
            else {
                //將數字字符串轉換成數字,然后存入棧中
                NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(s));
                System.out.println("數字入棧:"+s);
                numStack.push(numInterpreter);
            }
        }
        //最后當棧中不是空的時候繼續運算,直到棧為空即可
        while (!operatorStack.isEmpty()) {
            numStack.push(OperatorUtil.getInterpreter(numStack,operatorStack));
        }
    }

    /**
     * 計算結果出棧
     * @return
     */
    public int calculate() {
        return this.numStack.pop().interpret();
    }

    /**
     * 換成標准形式,便於分割
     * @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) == '-' ||
                expression.charAt(i) == '*' || expression.charAt(i) == '/')
                //在操作符與數字之間增加一個空格
                result += (" " + expression.charAt(i) + " ");
            else
                result += expression.charAt(i);
        }
        return result;
    }
}

此時,再來看客戶端測試代碼。


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

運行得到預期的結果,如下圖所示。

file

2 解釋器模式在JDK源碼中的應用

先來看JDK源碼中的Pattern對正則表達式的編譯和解析。


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 = new Start(lastAccept);
            matchRoot = lastAccept;
        }
    }
    ...
    public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }
    public static Pattern compile(String regex, int flags) {
        return new Pattern(regex, flags);
    }

    ...
}

3 解釋器模式在Spring源碼中的應用

再來看Spring中的ExpressionParser接口。


public interface ExpressionParser {

	Expression parseExpression(String expressionString) throws ParseException;

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

}

這里我們不深入講解源碼,通過我們前面編寫的案例大致能夠清楚其原理。不妨編寫一段客戶端代碼驗證一下。客戶端測試代碼如下。


    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("計算結果是:" + result);
    }
		
		```

運行結果如下圖所示。

![file](https://img2020.cnblogs.com/other/2593013/202111/2593013-20211118150609078-2013294383.png)

由上圖可知,運行結果與預期的結果是一致的。
**關注微信公眾號『 Tom彈架構 』回復“設計模式”可獲取完整源碼。**


> [【推薦】Tom彈架構:30個設計模式真實案例(附源碼),挑戰年薪60W不是夢](https://www.cnblogs.com/gupaoedu-tom/p/15484078.html)

> 本文為“Tom彈架構”原創,轉載請注明出處。技術在於分享,我分享我快樂!   
如果本文對您有幫助,歡迎關注和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力。關注微信公眾號『 Tom彈架構 』可獲取更多技術干貨!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM