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


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

    在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 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 是一致的算法。
    当然,并不是强求你把所有的枚举都替换成类模型来定义,这不是绝对的。还是要按照具体的业务逻辑来处理。

  • 相关阅读:
    YOLOPose实战:手把手实现单阶段的人体姿态估计+代码解读
    如何在CentOS7上搭建自己的GitLab仓库
    HDLBits-Edgecapture
    Fragment使用总结
    负载均衡策略
    第一:Python基于钉钉监控发送消息提醒
    Linux 磁盘挂载2(文件系统格式化、磁盘挂载、VFS虚拟化文件系统)
    【Go 基础篇】Go语言标识符解析:命名的艺术与最佳实践
    vue3动态引用本地图片不生效甚是报404
    (十四)Alian 的 Spring Cloud 订单服务调用自动生成的API
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/17468085.html