如何解決代碼中過多的 if else ?


先來一張鎮樓圖感覺一下 if else 的魔法吧。java

image.png

1、由一個幾百行 if 引起的思考

有個場景,50張字典表,須要爲其餘服務提供一個統一的接口來校驗用戶輸入的字典表 id 是否合法。編程

校驗邏輯已經很清晰了,根據參數選擇對應的表校驗 id 是否存在。app

if("table_a".equals(table)) {
      // check id
    }
    if("table_b".equals(table)) {
      // check id
    }
    if("table_c".equals(table)) {
      // check id
    }...

再加上參數校驗,函數調用,@Autowired bean 等等,一坨幾百行的代碼 ok 了。再新加表再加 if else 就好了,😋 完美。ide

如此,N 年後另外一個可憐的小夥伴就看到這坨東西。函數

2、KO 這些 if else

回想上面的場景,實際上就是要根據表名去肯定 id 是否存在表中,那麼只要將表名與操做對應起來就好了。故而採用哈希表的形式,將表名與操做對應起來。部分代碼以下:優化

// 用於保存表與 Function 的對應關係 
 private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);

 @PostConstruct
 private void init() {
    // map 初始化
    actionMappings.put(TableConstants.TABLE_A, (params) -> tableAManager.getById(params));
  }

/**
 * 校驗邏輯
 *
 *@param table
 *@param id
 */
 public boolean valid(String table, Long id) {
    Object object = actionMappings.get(table).apply(id);
    // 不存在則校驗失敗
    return !Objects.isNull(object);
 }

如此,N 多行 if 被消除了,這種編程方式也叫作表驅動。雖然 if 沒有了,可是在初始化 actionMappings 的時候仍是不少行重複代碼。下面採用註解方式解決:this

/**
 * 標記此註解的 bean 會加入基礎數據校驗全局 Function Map
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Documented
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidHandler {

  TABLE_ENUM value();
}

value 是表名枚舉,在須要的類上面加上此註解便可。同時定義一個 context 用來專門存儲 actionMappings 。spa

/**
 * 數據校驗上下文對象,用於保存各表的 Function Map
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Component
public class CommonDataValidContext {

  private static final Logger LOGGER = LoggerFactory.getLogger(CommonDataValidContext.class);

  private final Map<String, Function<Object, Object>> actionMappings = new ConcurrentHashMap<>(50);

  /**
   * 方法加入 mappings
   *
   * @param model 表名
   * @param action 方法
   */
  public void putAction(String model, Function<Object, Object> action) {
    if (!Objects.isNull(action)) {
      actionMappings.put(model, action);
      LOGGER.info(
          "[{}] add to CommonDataValidContext actionMappings, actionMappings size : {}",
          model,
          actionMappings.size());
    }
  }

  /**
   * 執行方法獲取返回結果
   *
   * @param model
   * @param param
   * @return
   */
  public <P, R> R apply(String model, P param) {
    if (actionMappings.containsKey(model)) {
      return (R) actionMappings.get(model).apply(param);
    } else {
      LOGGER.error("執行數據校驗時model={}不存在!", model);
      throw new RuntimeException("基礎數據校驗時發生錯誤:" + model + "表不存在!");
    }
  }

  /**
   * 判斷 mappings 中是否含有給定 model 的處理方法
   *
   * @param model
   * @return
   */
  public boolean containsKey(String model) {
    return actionMappings.containsKey(model);
  }

  /**
   * 校驗執行方法的返回值是否爲空
   *
   * @param model
   * @param param
   * @param <P>
   * @return
   */
  public <P> boolean validResultIsNull(String model, P param) {
    return Objects.isNull(this.apply(model, param));
  }
}

而後經過監聽器的方式,將含有 ValidHandler 註解的方法加入 actionMappings 。code

/**
 * 基礎數據校驗處理方法監聽器
 *
 * @author aysaml
 * @date 2020/5/7
 */
@Component
public class CommonValidActionListener implements ApplicationListener<ContextRefreshedEvent> {

  @Override
  public void onApplicationEvent(ContextRefreshedEvent event) {
    Map<String, Object> beans =
        event.getApplicationContext().getBeansWithAnnotation(ValidHandler.class);
    CommonDataValidContext commonDataValidContext =
        event.getApplicationContext().getBean(CommonDataValidContext.class);
    beans.forEach(
        (name, bean) -> {
          ValidHandler validHandler = bean.getClass().getAnnotation(ValidHandler.class);
          commonDataValidContext.putAction(
              validHandler.value().code(),
              (param) -> {
                try {
                  return bean.getClass().getMethod("getById", Long.class).invoke(bean, param);
                } catch (Exception e) {
                  e.printStackTrace();
                }
                return null;
              });
        });
  }
}

3、更多消除 if else 的方法。

1. 提早return

這樣可使代碼在邏輯表達上會更清晰,以下:對象

if (condition) {
 // do something
} else {
  return xxx;
}

按照逆向思惟來,優化以下:

if (!condition) {
  return xxx;
} 
// do something

還有一種常見的傻瓜編程(若有冒犯,敬請見諒,對碼不對人🙏 ):

if(a > 0) {
      return true;
    } else {
      return false;
    }

話很少說了,直接 return a > 0; 不香嗎?

2. 策略模式

簡單來講就是根據不一樣的參數執行不一樣的業務邏輯。
以下:

if (status == 0) {
  // 業務邏輯處理 0
} else if (status == 1) {
  // 業務邏輯處理 1
} else if (status == 2) {
  // 業務邏輯處理 2
} else if (status == 3) {
  // 業務邏輯處理 3
}...

優化以下:

  • 多態
interface A {
  void run() throws Exception;
}

class A0 implements A {
    @Override
    void run() throws Exception {
        // 業務邏輯處理 0
    }
}

class A1 implements A {
    @Override
    void run() throws Exception {
        // 業務邏輯處理 1
    }
}
// ...

而後策略對象存放在一個 Map 中,以下:

A a = map.get(param);
a.run();

2.2 枚舉

public enum Status {
    NEW(0) {
      @Override
      void run() {
        //do something  
      }
    },
    RUNNABLE(1) {
      @Override
       void run() {
         //do something  
      }
    };

    public int statusCode;

    abstract void run();

    Status(int statusCode){
        this.statusCode = statusCode;
    }
}

從新定義策略枚舉

public enum Aenum {
    A_0 {
      @Override
      void run() {
        //do something  
      }
    },
    A_1 {
      @Override
       void run() {
         //do something  
      }
    };
    //...
    abstract void run();
}

經過枚舉優化以後的代碼以下

Aenum a = Aenum.valueOf(param);
a.run();

3. Java 8 的 Optional

Optional主要用於非空判斷,是 Java 8 提供的新特性。

使用以前:

if (user == null) {
    //do action 1
} else {
    //do action2
}

若是登陸用戶爲空,執行action1,不然執行action 2,使用Optional優化以後,讓非空校驗更加優雅,間接的減小if操做

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

4. 決策表

就是上面的表驅動編程方法。

歡迎訪問 我的博客 獲取更多知識分享。
相關文章
相關標籤/搜索