為了減少程式碼複雜度,我將if-else升級為面向狀態程式設計

華為雲開發者社群發表於2021-10-08
摘要:程式導向設計和麵向物件設計的主要區別是:是否在業務邏輯層使用冗長的if else判斷。

本文分享自華為雲社群《從面向if-else程式設計升級為面向狀態程式設計,減少程式碼複雜度》,作者:breakDraw。

程式導向設計和麵向物件設計的主要區別是:是否在業務邏輯層使用冗長的if else判斷。如果你還在大量使用if else,當然,介面表現層除外,即使你使用Java/C#這樣完全物件導向的語言,也只能說明你的思維停留在傳統的程式導向語言上。

需求

有一個非常經典的數字校驗場景, 需求如下:
image.png

複雜度高的硬寫程式碼

這時候如果直接硬寫,大概率寫出容易複雜度巨高的程式碼,還容易遺漏而出錯。

例子如下:

class Solution {
    public boolean isNumber(String s) {
        int sign = 1;
        int pointSign = 1;
        int eSign = 1;
        int numSign = -1;
        int i = 0;
        int n = s.length();
        while(i<n){
            if(s.charAt(i)>='0'&&s.charAt(i)<='9'){
                numSign = 1;
                sign = -1;
            }else if(s.charAt(i)=='+'||s.charAt(i)=='-'){
                if(sign>0){
                    sign = -sign;
                }else{
                    return false;
                }
                if(i>0&&s.charAt(i-1)=='.'){
                    return false;
                }
            }else if(s.charAt(i)=='.'){
                //numSign = -1;
 
                if(pointSign>0){
                    pointSign = -pointSign;
                }else{
                    return false;
                }
                if(i>0&&(s.charAt(i-1)=='e'||s.charAt(i-1)=='E')){
                    return false;
                }
            }else if(s.charAt(i)=='e'||s.charAt(i)=='E'){
                if(eSign<0||numSign<0){
                    return false;
                }
                eSign = -1;
                sign = 1;
                numSign = -1;
                pointSign = -1;
            }else{
                return false;
            }
            i++;
        }
        return numSign>0;
    }
}

這段程式碼的複雜度為 21, 放在科目一考試直接不及格了,而且非常容易出錯,改著改著把自己改暈了,或者改漏了。
image.png

§ 狀態機優化

圖片引用自Leetcode官方題解,連結見:
https://leetcode-cn.com/probl...
image.png

可以看到校驗的過程可以組成一個狀態, 當遇到特定字元時,進入特定的狀態去判斷,並且該狀態後面只能接入有限的狀態。因此我們可以定義N個狀態,每個狀態定義X個狀態變化條件和變化狀態。

在java中用多個map即可進行維護這種關係。

可以寫出如下的程式碼, 雖然程式碼量看起來更高了,但是可維護性和複雜度變強不少。

    class Solution {
        public enum CharType {
            NUMBER,
            OP,
            POINT,
            E;
 
            public static CharType toCharType(Character c) {
                if (Character.isDigit(c)) {
                    return NUMBER;
                } else if (c == '+' || c == '-') {
                    return OP;
                } else if (c == '.') {
                    return POINT;
                } else if (c =='e' || c == 'E') {
                    return E;
                } else {
                    return null;
                }
            }
        }
        public enum State {
            INIT(false),
            OP1(false),
            // 在.前面的數字
            BEFORE_POINT_NUMBER(true),
            // 前面沒數字的點
            NO_BEFORE_NUMBER_POINT(false),
            // 前面有數字的點
            BEFORE_NUMBER_POINT(true),
            // 點後面的數字
            AFTER_POINT_NUMBER(true),
            // e/E
            OPE(false),
            // E後面的符號
            OP2(false),
            // e後面的數字
            AFTER_E_NUMBER(true);
 
            // 是否可在這個狀態結束
            private boolean canEnd;
 
            State(boolean canEnd) {
                this.canEnd = canEnd;
            }
 
            public boolean isCanEnd() {
                return canEnd;
            }
        }
 
        public Map<State, Map<CharType, State>> transferMap = new HashMap<>() {{
            Map<CharType, State> map = new HashMap<>() {{
                put(CharType.OP, State.OP1);
                put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
                put(CharType.POINT, State.NO_BEFORE_NUMBER_POINT);
            }};
            put(State.INIT, map);
 
            map = new HashMap<>() {{
                put(CharType.POINT, State.NO_BEFORE_NUMBER_POINT);
                put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
            }};
            put(State.OP1, map);
 
            map = new HashMap<>() {{
                put(CharType.POINT, State.BEFORE_NUMBER_POINT);
                put(CharType.NUMBER, State.BEFORE_POINT_NUMBER);
                put(CharType.E, State.OPE);
            }};
            put(State.BEFORE_POINT_NUMBER, map);
 
            map = new HashMap<>() {{
                put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
            }};
            put(State.NO_BEFORE_NUMBER_POINT, map);
 
            map = new HashMap<>() {{
                put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
                put(CharType.E, State.OPE);
            }};
            put(State.BEFORE_NUMBER_POINT, map);
 
            map = new HashMap<>() {{
                put(CharType.E, State.OPE);
                put(CharType.NUMBER, State.AFTER_POINT_NUMBER);
            }};
            put(State.AFTER_POINT_NUMBER, map);
            map = new HashMap<>() {{
                put(CharType.OP, State.OP2);
                put(CharType.NUMBER, State.AFTER_E_NUMBER);
            }};
            put(State.OPE, map);
            map = new HashMap<>() {{
                put(CharType.NUMBER, State.AFTER_E_NUMBER);
            }};
            put(State.OP2, map);
 
            map = new HashMap<>() {{
                put(CharType.NUMBER, State.AFTER_E_NUMBER);
            }};
            put(State.AFTER_E_NUMBER, map);
        }};
        public boolean isNumber(String s) {
            State state = State.INIT;
            for (char c : s.toCharArray()) {
                Map<CharType, State> transMap = transferMap.get(state);
                CharType charType = CharType.toCharType(c);
                if (charType == null) {
                    return false;
                }
                if (!transMap.containsKey(charType)) {
                    return false;
                }
                // 狀態變更
                state = transMap.get(charType);
            }
            return state.canEnd;
        }
    }

可以看到複雜度也只有8,不會複雜度超標。
image.png

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章