当前位置: 首页 >  电商服务 >  代码的坏味道(二)——为什么建议使用模型来替换枚举?

代码的坏味道(二)——为什么建议使用模型来替换枚举?

导读:为什么建议使用对象来替换枚举?.在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 Employee,他有职级,比如P6/P7。顺着这个思路,设计一个 Level 类型的枚举:.class Employee {.private String name;./**.* 薪

为什么建议使用对象来替换枚举?

在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 Employee,他有职级,比如P6/P7。顺着这个思路,设计一个 Level 类型的枚举:

class Employee {
      private String name;
      /**
       * 薪水
       */
      private int salary;
      /**
       * 工龄
       */
      private int workAge;
      /**
       * 职级
       */
      private Level level;
  }

  enum Level {
      P6, P7;
  }

假设哪天悲催的打工人毕业了,需要计算赔偿金,简单算法赔偿金=工资*工龄

   class EmployeeService {
        public int calculateIndemnity(int employeeId) {
            Employee employee=getEmployeeById(employeeId);
            return employee.workAge * employee.salary;
        }
   }

后来,随着这块业务逻辑的演进,其实公司是家具备人文关怀的好公司,再原有基础上,按照职级再额外补发一定的金额:

public int calculateIndemnity(int employeeId) {
    Employee employee = getEmployeeById(employeeId);
    switch (employee.level) {
        case P6:
            return employee.workAge * employee.salary + 10000;
        break;
        case P7:
            return employee.workAge * employee.salary + 20000;
        break;
        default:
            throw new UnsupportedOperationException("");
    }
}

当然,这段逻辑可能被重复定义,有可能散落在各个Service。
这里就出现了「代码的坏味道」
新的枚举值出现怎么办?
显然,添加一个新的枚举值是非常痛苦的,特别通过 switch 来控制流程,需要每一处都修改枚举,这也不符合开闭原则。而且,即使不修改,默认的防御性手段也会让那个新的枚举值将会抛出一个异常。

为什么会出现这种问题?
是因为我们定义的枚举是简单类型,无状态。

这个时候,需要用重新去审视模型,这也是为什么 DDD 是用来解决「大泥球」代码的利器。
一种好的实现方式是枚举升级为枚举类,通过设计「值对象」来重新建模员工等级:

abstract class EmployeeLevel {
    public static final EmployeeLevel P_6 = new P6EmployeeLevel(6, "资深开发");
    public static final EmployeeLevel P_7 = new P7EmployeeLevel(7, "技术专家");

    private int levle;
    private String desc;

    public EmployeeLevel(int levle, String desc) {
        this.levle = levle;
        this.desc = desc;
    }

    abstract int bouns();
}
class P6EmployeeLevel extends EmployeeLevel {
    public P6EmployeeLevel(int level, String desc) {
        super(level, desc);
    }

    @Override
    int bouns() {
        return 10000;
    }
}

static class P7EmployeeLevel extends EmployeeLevel {
    public P7EmployeeLevel(int level, String desc) {
        super(level, desc);
    }

    @Override
    int bouns() {
        return 20000;
    }
}

你看,这里叫「EmployeeLevel」,不是原先的「Level」,这名字可不是瞎取的。
这里,我把 EmployeeLevel 视为值类型,因为:
● 不可变的
● 不具备唯一性
通过升级之后的模型,可以把员工视为一个领域实体 Employee:

class Employee {
    private String name;
    /**
     * 薪水
     */
    private int salary;
    /**
     * 工龄
     */
    private int workAge;
    /**
     * 职级
     */
    private EmployeeLevel employeeLevel;

    public int calculateIndemnity() {
        return this.workAge * this.salary + employeeLevel.bouns();
    }
}

可以看到,计算赔偿金已经完全内聚到 Employee 实体中,我们设计领域实体的一个准则是:必须是稳定的,要符合高内聚,同时对扩展是开放的,对修改是关闭的。你看,哪天 P8 被裁了,calculateIndemnity 是一致的算法。
当然,并不是强求你把所有的枚举都替换成类模型来定义,这不是绝对的。还是要按照具体的业务逻辑来处理。

内容
  • 一文揭秘DDD到底解决了什么问题
    一文揭秘DDD到底解决了什么问题
    2023-12-01
    DDD作为架构设计思想帮助微服务控制规模复杂度,那它是怎么做到的呢?.一、架构设计是为了解决系统复杂度.谈到架构,相信每
  • Unity实现3D物体遮挡血条
    Unity实现3D物体遮挡血条
    2023-12-08
    Unity 实现3D物体遮挡血条.######.前言:在游戏开发中,我们经常会遇到UI和3D物体的层级遮挡问题,最常见的
  • 一个公式让你35岁以后能越过越好!大神修炼心法
    一个公式让你35岁以后能越过越好
    2023-12-08
    前言.Cocos 的老铁,如果你这几天没有被麒麟子给卷到?那说明你还没有真正进入 Cocos 圈子里来。为什么这么说呢?
  • C++学习-static
    C++学习-static
    2023-12-02
    全局变量使用:.作用是限定全局变量的作用范围,只能在当前文件使用,类似给它加了个private属性。.其他文件即使使用e
  • 【Oculus Interaction SDK】(五)设置不同的抓握手势
    【Oculus Interact
    2023-12-10
    前言.前段时间 Oculus 的 SDK.频繁更新,很多已有的教程都不再适用于现在的版本了。本系列文章的主要目的是记录现
  • 吃透单调栈(2)——解两道Hard题:接雨水、柱状图中最大的矩形问题
    吃透单调栈(2)——解两道Har
    2023-12-04
    怎么想到要用单调栈的?.这类题目的数据通常是一维数组,要寻找任一个元素的右边或者左边第一个 比自己大 或者小 的元素的位