###為什麼建議使用對象來替換枚舉? ### 在設計模型時,我們經常會使用枚舉來定義類型,比如說,一個員工類 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 是一致的演算法。
當然,並不是強求你把所有的枚舉都替換成類模型來定義,這不是絕對的。還是要按照具體的業務邏輯來處理。