

代码的坏味道(二)——为什么建议使用模型来替换枚举? - 木宛城主
source link: https://www.cnblogs.com/OceanEyes/p/17468085.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

阅读目录
为什么建议使用对象来替换枚举?
在设计模型时,我们经常会使用枚举来定义类型,比如说,一个员工类 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 是一致的算法。
当然,并不是强求你把所有的枚举都替换成类模型来定义,这不是绝对的。还是要按照具体的业务逻辑来处理。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK