找回密码
 立即注册
首页 业界区 业界 代码的坏味道(二)——为什么建议使用模型来替换枚举? ...

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

云卦逾 6 天前
为什么建议使用对象来替换枚举?

在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 Employee,他有职级,比如P6/P7。顺着这个思路,设计一个 Level 类型的枚举:
  1. class Employee {
  2.       private String name;
  3.       /**
  4.        * 薪水
  5.        */
  6.       private int salary;
  7.       /**
  8.        * 工龄
  9.        */
  10.       private int workAge;
  11.       /**
  12.        * 职级
  13.        */
  14.       private Level level;
  15.   }
  16.   enum Level {
  17.       P6, P7;
  18.   }
复制代码
假设哪天悲催的打工人毕业了,需要计算赔偿金,简单算法赔偿金=工资*工龄
  1.    class EmployeeService {
  2.         public int calculateIndemnity(int employeeId) {
  3.             Employee employee=getEmployeeById(employeeId);
  4.             return employee.workAge * employee.salary;
  5.         }
  6.    }
复制代码
后来,随着这块业务逻辑的演进,其实公司是家具备人文关怀的好公司,再原有基础上,按照职级再额外补发一定的金额:
  1. public int calculateIndemnity(int employeeId) {
  2.     Employee employee = getEmployeeById(employeeId);
  3.     switch (employee.level) {
  4.         case P6:
  5.             return employee.workAge * employee.salary + 10000;
  6.         break;
  7.         case P7:
  8.             return employee.workAge * employee.salary + 20000;
  9.         break;
  10.         default:
  11.             throw new UnsupportedOperationException("");
  12.     }
  13. }
复制代码
当然,这段逻辑可能被重复定义,有可能散落在各个Service。
这里就出现了「代码的坏味道」
新的枚举值出现怎么办?
显然,添加一个新的枚举值是非常痛苦的,特别通过 switch 来控制流程,需要每一处都修改枚举,这也不符合开闭原则。而且,即使不修改,默认的防御性手段也会让那个新的枚举值将会抛出一个异常。
为什么会出现这种问题?
是因为我们定义的枚举是简单类型,无状态。
这个时候,需要用重新去审视模型,这也是为什么 DDD 是用来解决「大泥球」代码的利器。
一种好的实现方式是枚举升级为枚举类,通过设计「值对象」来重新建模员工等级:
  1. abstract class EmployeeLevel {
  2.     public static final EmployeeLevel P_6 = new P6EmployeeLevel(6, "资深开发");
  3.     public static final EmployeeLevel P_7 = new P7EmployeeLevel(7, "技术专家");
  4.     private int levle;
  5.     private String desc;
  6.     public EmployeeLevel(int levle, String desc) {
  7.         this.levle = levle;
  8.         this.desc = desc;
  9.     }
  10.     abstract int bouns();
  11. }
  12. class P6EmployeeLevel extends EmployeeLevel {
  13.     public P6EmployeeLevel(int level, String desc) {
  14.         super(level, desc);
  15.     }
  16.     @Override
  17.     int bouns() {
  18.         return 10000;
  19.     }
  20. }
  21. static class P7EmployeeLevel extends EmployeeLevel {
  22.     public P7EmployeeLevel(int level, String desc) {
  23.         super(level, desc);
  24.     }
  25.     @Override
  26.     int bouns() {
  27.         return 20000;
  28.     }
  29. }
复制代码
你看,这里叫「EmployeeLevel」,不是原先的「Level」,这名字可不是瞎取的。
这里,我把 EmployeeLevel 视为值类型,因为:
● 不可变的
● 不具备唯一性
通过升级之后的模型,可以把员工视为一个领域实体 Employee:
  1. class Employee {
  2.     private String name;
  3.     /**
  4.      * 薪水
  5.      */
  6.     private int salary;
  7.     /**
  8.      * 工龄
  9.      */
  10.     private int workAge;
  11.     /**
  12.      * 职级
  13.      */
  14.     private EmployeeLevel employeeLevel;
  15.     public int calculateIndemnity() {
  16.         return this.workAge * this.salary + employeeLevel.bouns();
  17.     }
  18. }
复制代码
可以看到,计算赔偿金已经完全内聚到 Employee 实体中,我们设计领域实体的一个准则是:必须是稳定的,要符合高内聚,同时对扩展是开放的,对修改是关闭的。你看,哪天  P8 被裁了,calculateIndemnity 是一致的算法。
当然,并不是强求你把所有的枚举都替换成类模型来定义,这不是绝对的。还是要按照具体的业务逻辑来处理。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册