zoukankan      html  css  js  c++  java
  • 为了减少代码复杂度,我将if-else升级为面向状态编程

    摘要:面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。

    本文分享自华为云社区《从面向if-else编程升级为面向状态编程,减少代码复杂度》,作者:breakDraw。

    面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断。如果你还在大量使用if else,当然,界面表现层除外,即使你使用Java/C#这样完全面向对象的语言,也只能说明你的思维停留在传统的面向过程语言上。

    需求

    有一个非常经典的数字校验场景, 需求如下:

    复杂度高的硬写代码

    这时候如果直接硬写,大概率写出容易复杂度巨高的代码,还容易遗漏而出错。

    例子如下:

    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, 放在科目一考试直接不及格了,而且非常容易出错,改着改着把自己改晕了,或者改漏了。

    § 状态机优化

    图片引用自Leetcode官方题解,链接见:
    https://leetcode-cn.com/problems/valid-number/solution/you-xiao-shu-zi-by-leetcode-solution-298l/

    可以看到校验的过程可以组成一个状态, 当遇到特定字符时,进入特定的状态去判断,并且该状态后面只能接入有限的状态。因此我们可以定义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,不会复杂度超标。

     

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

  • 相关阅读:
    数据库根据两列数据得到层级关系SQL Queries to Manage Hierarchical or Parent-child Relational Rows in SQL Server
    escape from braxis site:us.forums.blizzard.com
    LEFT JOIN vs. LEFT OUTER JOIN in SQL Server
    Why do I have more rows after LEFT JOIN?
    Why is the IIS default app pool recycle set to 1740 minutes?
    IIS Best Practices
    USB挂载&U盘启动
    OPKG 介绍
    OPKG 配置
    OPKG 软件包管理
  • 原文地址:https://www.cnblogs.com/huaweiyun/p/15378591.html
Copyright © 2011-2022 走看看