美文网首页
利用“充血枚举类型”消除if/else

利用“充血枚举类型”消除if/else

作者: 半路和尚怎么出家 | 来源:发表于2020-02-15 18:11 被阅读0次

截取自:https://mp.weixin.qq.com/s/HEyPBwbQZVRbFL1ivruVbg

无处不在的 if else 牛皮癣

无论你的编程启蒙语言是什么,最早学会的逻辑控制语句一定是 if else,但是不幸的是它在你开始真正的编程工作以后,会变成一个损害项目质量的坏习惯。

几乎所有的项目都存在 if else 泛滥的问题,但是却没有引起足够重视警惕,甚至被很多程序员认为是正常现象。

首先我来解释一下为什么 if else 这个看上去人畜无害的东西是有害的、是需要严格管控的

  • if else if ...else 以及类似的 switch 控制语句,本质上是一种 hard coding 硬编码行为,如果你同意“magic number 魔法数字”是一种错误的编程习惯,那么同理,if else 也是错误的 hard coding 编程风格;
  • hard coding 的问题在于当需求发生改变时,需要到处去修改,很容易遗漏和出错;
  • 以一段代码为例来具体分析:
if ("3".equals(object.getString("type"))){
          String data = object.getString("data");
          Integer zmf = JSON.parseObject(data).getJSONObject("taobao_user_info").getInteger("zm_score");
          Map param = new HashMap();
          param.put("phoneNumber", object.getString("mobile"));
          List<Dperson> list1 = personBaseDaoI.find("from xyz where phoneNumber=:phoneNumber", param);
          if (list1 !=null){
              for (Dperson dperson:list1){
                  dperson.setZmScore(zmf);
                  personBaseDaoI.saveOrUpdate(dperson);
                  AppFlowUtil.updateAppUserInfo(dperson.getToken(),null,null,zmf);
              }
          }
}
  • if ("3".equals(object.getString("type")))

    • 显然这里的"3"是一个 magic number,没人知道 3 是什么含义,只能推测;
    • 但是仅仅将“3”重构成常量 ABC_XYZ 并不会改善多少,因为 if (ABC_XYZ.equals(object.getString("type"))) 仍然是面向过程的编程风格,无法扩展;
    • 到处被引用的常量 ABC_XYZ 并没有比到处被 hard coding 的 magic number 好多少,只不过有了含义而已;
    • 把常量升级成 Enum 枚举类型呢,也没有好多少,当需要判断的类型增加了或判断的规则改变了,还是需要到处修改——Shotgun Surgery(霰弹式修改)
  • 并非所有的 if else 都有害,比如上面示例中的 if (list1 !=null) { 就是无害的,没有必要去消除,也没有消除它的可行性。判断是否有害的依据:

    • 如果 if 判断的变量状态只有两种可能性(比如 boolean、比如 null 判断)时,是无伤大雅的;
    • 反之,如果 if 判断的变量存在多种状态,而且将来可能会增加新的状态,那么这就是个问题;
    • switch 判断语句无疑是有害的,因为使用 switch 的地方往往存在很多种状态。

充血枚举类型——Rich Enum Type

正如前面分析呈现的那样,对于代码中广泛存在的状态、类型 if 条件判断,仅仅把被比较的值重构成常量或 enum 枚举类型并没有太大改善——使用者仍然直接依赖具体的枚举值或常量,而不是依赖一个抽象。

于是解决方案就自然浮出水面了:在 enum 枚举类型基础上进一步抽象封装,得到一个所谓的“充血”的枚举类型,代码说话:

  • 实现多种系统通知机制,传统做法:
enum NOTIFY_TYPE {    email,sms,wechat;  }  //先定义一个enum——一个只定义了值不包含任何行为的“贫血”的枚举类型

if(type==NOTIFY_TYPE.email){ //if判断类型 调用不同通知机制的实现 
    。。。
}else if (type=NOTIFY_TYPE.sms){
    。。。
}else{
    。。。
}
  • 实现多种系统通知方式,充血枚举类型——Rich Enum Type 模式:
enum NOTIFY_TYPE {    //1、定义一个包含通知实现机制的“充血”的枚举类型
  email("邮件",NotifyMechanismInterface.byEmail()),
  sms("短信",NotifyMechanismInterface.bySms()),
  wechat("微信",NotifyMechanismInterface.byWechat());  
  
  String memo;
  NotifyMechanismInterface notifyMechanism;
  
  private NOTIFY_TYPE(String memo,NotifyMechanismInterface notifyMechanism){//2、私有构造函数,用于初始化枚举值
      this.memo=memo;
      this.notifyMechanism=notifyMechanism;
  }
  //getters ...
}

public interface  NotifyMechanismInterface{ //3、定义通知机制的接口或抽象父类
    public boolean doNotify(String msg);
 
    public static NotifyMechanismInterface byEmail(){//3.1 返回一个定义了邮件通知机制的策的实现——一个匿名内部类实例
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    }
    public static NotifyMechanismInterface bySms(){//3.2 定义短信通知机制的实现策略
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    } 
    public static NotifyMechanismInterface byWechat(){//3.3 定义微信通知机制的实现策略
        return new NotifyMechanismInterface(){
            public boolean doNotify(String msg){
                .......
            }
        };
    }
}

//4、使用场景
NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);
  • 充血枚举类型——Rich Enum Type 模式的优势:

    • 不难发现,这其实就是 enum 枚举类型和 Strategy Pattern 策略模式的巧妙结合运用;
    • 当需要增加新的通知方式时,只需在枚举类 NOTIFY_TYPE 增加一个值,同时在策略接口 NotifyMechanismInterface 中增加一个 by 方法返回对应的策略实现;
    • 当需要修改某个通知机制的实现细节,只需修改 NotifyMechanismInterface 中对应的策略实现;
    • 无论新增还是修改通知机制,调用方完全不受影响,仍然是NOTIFY_TYPE.valueof(type).getNotifyMechanism().doNotify(msg);
  • 与传统 Strategy Pattern 策略模式的比较优势:常见的策略模式也能消灭 if else 判断,但是实现起来比较麻烦,需要开发更多的 class 和代码量:

    • 每个策略实现需单独定义成一个 class;
    • 还需要一个 Context 类来做初始化——用 Map 把类型与对应的策略实现做映射;
    • 使用时从 Context 获取具体的策略;
  • Rich Enum Type 的进一步的充血:

    • 上面的例子中的枚举类型包含了行为,因此已经算作充血模型了,但是还可以为其进一步充血;
    • 例如有些场景下,只是要对枚举值做个简单的计算获得某种 flag 标记,那就没必要把计算逻辑抽象成 NotifyMechanismInterface 那样的接口,杀鸡用了牛刀;
    • 这时就可以在枚举类型中增加 static function 封装简单的计算逻辑;
  • 策略实现的进一步抽象:

    • 当各个策略实现(byEmail bySms byWechat)存在共性部分、重复逻辑时,可以将其抽取成一个抽象父类;
    • 然后就像前一章节——业务模板 Pattern of NestedBusinessTemplate 那样,在各个子类之间实现优雅的逻辑分离和复用。

相关文章

  • 利用“充血枚举类型”消除if/else

    截取自:https://mp.weixin.qq.com/s/HEyPBwbQZVRbFL1ivruVbg 无处不...

  • 统一异常处理

    首先,利用枚举,来定义异常类型。定义枚举ResultEnum: 自定义异常,新建CustomException类:...

  • 枚举中添加新方法消除IF/ELSE

    一、如下需求:通过JAVA代码实现根据空气指数在对应区间获取空气质量分类 二、通常实现以上需求逻辑代码是非常简单的...

  • C语言基础 之 枚举类型

    枚举类型 枚举类型: 列出所有可能的值 枚举类型的定义 枚举类型定义的一般格式:enum 枚举类型名 {枚举值表}...

  • Swift 基础笔记 - 枚举

    枚举 OC定义和使用枚举 Swift定义枚举类型 Swift判断枚举类型 枚举成员类型

  • 线程安全的singleton,双检锁

    少了一个枚举的方式。利用枚举类型来创建Singleton。下面的文章案例很清晰,看文章。https://blog....

  • Java的枚举类型用法介绍

    背景 在java语言中还没有引入枚举类型之前,表示枚举类型的常用模式是声明一组具有int常量的类。之前我们通常利用...

  • 利用枚举类解决过多if-else问题

    实际开发过程中经常遇到很多的条件判断,一层一层,对于有强迫症的我来说,很不习惯本文通过枚举来解决,废话不多说,代码...

  • 枚举

    枚举 枚举就是专门用来表示几种固定类型的取值 枚举的本质就是基本数据类型,整型 枚举类型定义格式 定义枚举类型变量...

  • JavaScript中的Enum枚举类型数据

    一、枚举类型介绍 1、枚举类型 如果接触过其它语言或者TypeScript,大概对于枚举类型有一些了解。枚举类型是...

网友评论

      本文标题:利用“充血枚举类型”消除if/else

      本文链接:https://www.haomeiwen.com/subject/doeyfhtx.html