4

为了减少代码复杂度,我将if-else升级为面向状态编程

 2 years ago
source link: https://segmentfault.com/a/1190000040777766
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

摘要:面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的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

点击关注,第一时间了解华为云新鲜技术~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK