写代码的程序员许多,写好代码的程序员却不是那么多(愿你我都在路上)。今天共享一个避免很多if-else的案例,和咱们共同进步。

该篇是笔者在Flutter项目中遇到的问题,所以示例代码是Dart语言。请读者不要有担负,语言不是重点,重点是思想。

问题的由来

下面是两段实际的业务代码:

  1. 购买操作,再履行真正的购买流程之前,需求有必要的条件校验

    _onBuyButtonClick() {
      /// 1. 用户封禁校验
      final user = getUserInfo();
      if (user.isForbidden) {
        showForbiddenDialog();
        return;
      }
      /// 2. 未付出订单数量校验
      final orders = getUserWaitingPayOrders();
      if (orders.length >= limit) {
        showTooMuchOrderWaitingPayDialog();
        return;
      }
      /// 3. xxx
      /// 4. xxx
      /// 购买流程
    }
    
  2. 出售操作,再履行真正的出售流程之前,需求有必要的条件校验

    _onSellButtonClick() {
      /// 1. 用户封禁校验
      final user = getUserInfo();
      if (user.isForbidden) {
        showForbiddenDialog();
        return;
      }
      /// 2. 店铺封禁校验
      /// 3. xxx
      /// 4. xxx
      /// 售卖流程
    }
    

这样需求校验的流程,咱们一共有10个!每个流程需求校验的项目2~5个不等。咱们体验下需求文档

如何避免大量的if-else?
如何避免大量的if-else?

这儿的每一张图代表一个流程,每个黄色方块是一个校验项目。当然,流程是不可能重复的,但每个校验项可能是重复的。所以咱们能够将问题笼统为:如安在N个操作加入M个前置验证?例如:

  1. 操作N1(购买)需求查看M1(用户是否被封禁)、M2(等待付款的订单不能太多)...
  2. 操作N2(上架出售)需求查看M1、M3(店铺是否被封禁)、M4(正在售卖的商品是否达到数量上限)...
  3. 操作N3需求验证M5、M6、M8….

关于这种非常合理的需求,咱们怎么能辩驳呢? So, let’s kill it!

解决方案

Show me the code。先给出现在正在运用的方案,再剖析里边的详细细节。

咱们终究完成的作用如下(以购买流程为例):

_onBuyButtonClick() {
  /// 运用CheckController来操控哪些条件是需求被查看的
  final anyChecker = CheckController().check([
    Requirement.account,
    Requirement.orderBuffer,
  ]);
  /// 若存在需求处理的,就处理它
  if (anyChecker != null) {
    anyChecker.handle();
    return;
  }
  /// 之前的购买流程
}

能够看到,咱们将本来的几十行的校验代码(最长的8个校验项目,也就是8个if判别),缩短为短短的几行。相比之下,该方案有许多长处:

  1. 没有重复代码。之前N2流程中的校验代码,彻底是N1Copy。现在即便两个流程具有相同的校验项,也只体现在枚举的相同case上。
  2. 可读性增强,可维护性大大提高。在很多的if-else中搞懂它是做什么的,尽管不是很有挑战,但它的确需求必定时刻。特别是在一段时刻之后,加上没有详细注释的情况下。
  3. 可维护性大大提高。一个流程的校验项,彻底对应数组的元素,包括校验项的增删改查。假设在一个流程上改变两个项目的优先级,之前你需求读懂哪两个if是你关心的,然后才干调整。现在,你只需求在数组中找到对应的case就能够。并且现在它们是肯定集合的,之前的代码可能一部分在屏幕可见规模,另一部分彻底不在!

怎么完成

假如你对上面的完成感兴趣的话,这儿咱们一起剖析它是怎么完成的。

第一阶段 – 减少重复性

若想复用M个查看,咱们必须将查看部分的代码独立出来。以购买的查看为例,咱们能够发现整个过程能够分为两步:

  1. 条件校验
  2. 成果处理 一切M个查看都能够看做,校验xxx条件,不满足的话就xxx。这儿咱们将每个查看封装成独立的类,以购买中的用户是否被封禁查看为例:
class AccountForbiddenChecker {
  /// 依据条件回来用户是否被封禁
  bool match() {
    return false;
  }
  /// 用户被封禁的详细操作,如弹窗正告
  void handle() {}
}

再比如等待付款的订单不能太多的查看:

class OrderWaitingPayBufferChecker {
  /// 判别用户未付出的订单是否太多
  bool match() {
    return false;
  }
  /// 未付出订单过多的详细操作,如弹窗正告
  void handle() {}
}

像这样,咱们能够将这M个查看,都封装在详细的类中。避免了多处流程条件查看中的复制粘贴。但关于运用者来说,他需求记住每一种Checker的姓名,最起码需求有形象,这是一种担负。所以,咱们运用枚举,来表示每一个查看项:

/// 需求校验的项目
enum Requirement {
  account,
  orderBuffer,
  // ...
}

由枚举到详细的查看类,咱们还需求有个转化过程。这儿运用了switch

extension Mapper on Requirement {
    RequirementChecker toChecker() {
        switch(this) {
            case Requirement.account: return AccountForbiddenChecker();
            case Requirement.orderBuffer: return OrderWaitingPayBufferChecker();
            // ...
        }
    }
}

第二阶段 – 添加可复制性

当需求协调多个类的时候,咱们就需求一个管理者了。

/// 查看项管理器
class CheckController {
  /// 依据传入的枚举,判别详细的项目是否匹配,若匹配,则回来对应的查看者。
  RequirementChecker? check(List<Requirement> items) {
    for (final item in items) {
      final checker = item.toChecker();
      if (checker.match()) {
        return checker;
      }
    }
    return null;
  }
}

RequirementChecker也是必要的,它是一个接口,负责标准化每个Checker

abstract class RequirementChecker {
  bool match();
  void handle();
}

然后每个详细的Checker完成该接口,这样管理者,以及外部才干统一运用多个Checker

到这儿,咱们就完成了上面的解决方案。关于每个流程,咱们只需求CV大法,然后对校验项稍作修正,即可达到作用。bingo

相关引荐

今天的解决方案并不是笔者初创。其思想来源于规划形式中的职责链形式。墙裂引荐这个网站。

关于每种形式,都配有很多的图解、问题以及解决方案。例如职责链形式一章:

如何避免大量的if-else?
如何避免大量的if-else?
如何避免大量的if-else?
简直不要太赞!

好了,秘籍都奉上了。希望咱们提前登峰造极!