背景
開發(fā) Java 項目時, 數(shù)據(jù)字典的管理是個令人頭痛的問題, 至少對我而言是這樣的, 我所在的上一家公司項目里面對于字典表的管理是可以進行配置的, 他們是將字典表統(tǒng)一存放在一個數(shù)據(jù)庫里面進行配置, 然后可以由管理員進行動態(tài)的實現(xiàn)字典表的變更.
數(shù)據(jù)結(jié)構(gòu)表
先來兩個數(shù)據(jù)表(簡單一點, 一些非空, 長度什么的就不寫了), 兩個表都有 gender
和 state
, gender
字典項相同, 但 state
字典項不同
-
學(xué)生表 Student
字段名(field) 類型 字典項 stuNo INTEGER name VARCHAR gender VARCHAR 性別 : {男, 女} state VARCHAR 狀態(tài) : {未報到, 在讀, 畢業(yè), 結(jié)業(yè), 肄業(yè), 退學(xué), 開除} -
教師表 Teacher
字段名(field) 類型 字典項 teaNo INTEGER name VARCHAR gender VARCHAR 性別 : {男, 女} state VARCHAR 狀態(tài) : {未報到, 在職, 離職, 開除}
使用枚舉來管理數(shù)據(jù)字典
-
以上面的
Student
中的字段gender
為例, 一般枚舉來管理字典表的寫法大致如下package cn.cpf.test; public class DicEnum1 { /** * 性別 */ enum Gender { MAN("1", "男"), WOMAN("2", "女"); private final String value; private final String label; Gender(String value, String label) { this.value = value; this.label = label; } public String getCode() { return value; } public String getText() { return label; } } }
枚舉的增強使用(枚舉里加方法)
枚舉的好處遠遠沒有這么簡單
例如這個時候, 我想通過一個字典的 value 直接獲取到這個枚舉的 label, 那么可以在里面增加一個方法
- 在數(shù)據(jù)字段
Gender
中, 通過代碼獲取文本(eg: 通過代碼1
來獲取男
這個文本). - 在數(shù)據(jù)字段
Gender
中, 通過代碼獲取整個枚舉對象(eg: 通過文本1
來獲取MAN
這個枚舉). - 在數(shù)據(jù)字段
Gender
中, 判斷一個代碼是否對應(yīng)這個枚舉(eg: 判斷1
是否是MAN
這個枚舉).
然后整個枚舉對象就變成了這樣
public class DicEnum1 {
/**
* 性別
*/
enum Gender {
MAN("1", "男"), WOMAN("2", "女");
private final String value;
private final String label;
Gender(String value, String label) {
this.value = value;
this.label = label;
}
/**
* 通過 value 獲取 整個枚舉對象
*/
public static Gender getByCode(String value) {
for (Gender item : Gender.values()) {
if (item.value == value) {
return item;
}
}
return null;
}
/**
* 通過 value 獲取 label
*/
public static String getTextByCode(String value) {
Gender byCode = getByCode(value);
if (byCode == null) {
return "";
}
return byCode.getText();
}
/**
* 當前對象的 value 是否 和參數(shù)中的 value 相等
*/
public boolean isCode(String value) {
return this.value.equals(value);
}
/**
* 當前對象是否和已知對象不等
*/
public boolean isNotEquals(Gender gender) {
return this != gender;
}
public String getCode() { return value; }
public String getText() { return label; }
}
}
看到這里估計就算是沒有強迫癥的人也覺得枚舉真的是太麻煩了, 一般一個普通項目中也都有幾十上百個枚舉, 一個枚舉都這么麻煩, 那么大項目中成百上千個枚舉的話還怎么搞啊!
那么有沒有什么方法能夠?qū)⒚杜e變得簡單點呢?
枚舉的優(yōu)化策略
按照上面的寫法, 里面的很多方法都是可以相同的, 甚至連 value, 和 label 成員變量都是相同的, 那么像這類重復(fù)代碼使用繼承是最好不過的.
然而枚舉中是不能夠使用繼承的, 至于之后的 jdk
能不能實現(xiàn)枚舉繼承我們先不討論, 現(xiàn)在至少 jdk1.8
版本的枚舉是不能夠使用繼承的.
那么我們還有其他辦法嗎, 不要著急, 辦法肯定有; 讓我們一步步分析.
枚舉是不能夠使用繼承的, 但是可以實現(xiàn)接口, 尤其是 1.8 版本之后, 通過接口里面的默認方法, 簡直和繼承抽象類很相像了有沒有.
那么我們可以添加一個接口 IDictItem
. 可是問題又來了, 我們該怎么樣是的枚舉和接口聯(lián)系起來呢.
首先枚舉 Gender
里面的靜態(tài)方法 public static String getTextByCode(String value)
& public static Gender getByCode(String value)
, 我們可以將它放在接口里面作為一個通用的靜態(tài)方法, 而枚舉里面的成員函數(shù)我們可以將它們變成 IDictItem
中的默認方法.
第一步優(yōu)化 : 枚舉繼承接口
-
接口類
package cn.cpf.test; /** * @Author CPF * @Date 21:25 2019/3/3 * @Description 字典表接口 **/ public class IDictItem { String getCode(); String getText(); /** * 通過 value 獲取指定 枚舉類型中的 枚舉對象 * * @param enumClass * @param value * @param <T> * @return */ static <T extends IDictItem> T getByCode(Class<T> enumClass, String value) { //通過反射取出Enum所有常量的屬性值 for (T each : enumClass.getEnumConstants()) { //利用value進行循環(huán)比較睡雇,獲取對應(yīng)的枚舉 if (value.equals(each.getCode())) { return each; } } return null; } /** * 通過 value 獲取指定 枚舉類型中的 label * * @param enumClass * @param value * @param <T> * @return */ static <T extends IDictItem> String getTextByCode(Class<T> enumClass, String value) { IDictItem byCode = getByCode(enumClass, value); if (null == byCode) { return null; } return byCode.getCode(); } /** * 當前對象的 value 是否 和參數(shù)中的 value 相等 * @param value * @return */ default boolean isCode(String value) { return this.getCode().equals(value); } /** * 當前對象是否和已知對象不等 * @param gender * @return */ default boolean isNotEquals(IDictItem gender) { return this != gender; } }
-
枚舉類
/** * 性別 */ enum Gender implements IDictItem { MAN("1", "男"), WOMAN("2", "女"); private final String value; private final String label; Gender(String value, String label) { this.value = value; this.label = label; } @Override public String getCode() { return value; } @Override public String getText() { return label; } }
這樣的確是簡單了許多, 但是對于我這種強迫癥患者來說, 那么多枚舉里面的
label
, 和value
成員變量以及它們的 get 方法也都是相同的啊, 能不能把這些代碼給消除掉呢,
第二步優(yōu)化 : 增加 Bean 存枚舉值, 使用享元模式存儲 Bean
思路
我仔細考慮了一下, 對于一個有參數(shù)的枚舉來說, 在初始化的時候類加載器會首先執(zhí)行枚舉項, 也就是調(diào)用枚舉的構(gòu)造方法,
以
Gender
為例, 初始化時, 首先執(zhí)行MAN("1", "男")
,WOMAN("2", "女")
, 調(diào)用Gender(String value, String label)
, 將參數(shù)label
, 和value
存至枚舉對象的label
, 和value
成員變量中, 之后我們通過get
方法獲取成員變量label
, 和value
的值來使用枚舉.因此如果我們想要消除
label
, 和value
成員變量, 那么必須給他們一個存儲的空間來存取它們, 例如可以使用一個 map 來保存它們.
具體實現(xiàn)方案 :
我們先將枚舉的參數(shù) label
, 和 value
封裝成一個普通java對象 DicCodeBean
, 然后通過享元模式將這些 DicCodeBean
存放在 DicCodePool
, DicCodePool
里面就是一個map, 再添加一個get, 和put 方法.
代碼
-
DicCodeBean
: 用來封裝參數(shù)label
和value
public class DicCodeBean { public final String value; public final String label; public DicCodeBean(String value, String label) { this.value = value; this.label = label; } }
-
DicCodePool
用來存放DicCodeBean
/** * @Author CPF * @Date 14:17 2019/2/26 * @Description 字典表對象池 **/ class DicCodePool { private DicCodePool() {} /** * 用于存儲字典數(shù)據(jù) */ private static final Map<IDictItem, DictItemBean> dictItemMap = new ConcurrentHashMap<>(); /** * 往 map 中添加代碼項 */ public static void putDictItem(IDictItem iCodeItem, String value, String label) { dictItemMap.put(iCodeItem, DictItemBean.of(value, label)); } /** * 獲取靜態(tài)數(shù)據(jù) */ public static DictItemBean getDictItem(IDictItem iDictItem) { return dictItemMap.get(iDictItem); } }
-
那么接下來我們的枚舉類就可以簡化成這樣
/** * 性別 */ enum Gender implements IDictItem { MAN("1", "男"), WOMAN("2", "女"); Gender(String value, String label) { putDictItem(value, label); } }
示例
接下來實際演示一下這種方式的優(yōu)勢, 例如上面的兩張表, 我們就可以寫成下面的代碼
-
學(xué)生字典表
public class DicStudent { /** * 性別 */ enum Gender implements IDictItem { MAN("1", "男"), WOMAN("2", "女"); Gender(String value, String label) { putDictItem(value, label); } } /** * 狀態(tài) */ enum State implements IDictItem { READING("10", "在讀"), GRADUATION("20", "畢業(yè)"), DEFAMATION("30", "肄業(yè)"), WITHDRAWAL("40", "退學(xué)"), EXPULSION("50", "開除"); State(String value, String label) { putDictItem(value, label); } } }
-
教師字典表
public class DicTeacher { /** * 性別 */ enum Gender implements IDictItem { MAN("1", "男"), WOMAN("2", "女"); Gender(String value, String label) { putDictItem(value, label); } } /** * 狀態(tài) */ enum State implements IDictItem { WORK("10", "在職"), RESIGNED("20", "離職"), EXPELLED("30", "開除"); State(String value, String label) { putDictItem(value, label); } } }
是不是很簡單, 每一張表對應(yīng)一個枚舉管理類, 表中的字典項, 對應(yīng)類中的一個枚舉類, 很方便的將各個枚舉分離出來, 而且在使用的時候, 利用IDE工具的提示, 可以非常方便地進行編寫, 而且利用枚舉里面的方法可以降低很多代碼哦.
使用枚舉管理數(shù)據(jù)字典的好處
-
使用簡單方便, 用到一張表的某個含有數(shù)據(jù)字典的字段的時候, 能夠根據(jù)IDE的代碼提示輕松獲取字段的各個信息.
防止出錯, 易于維護, 因為使用的是枚舉, 使用的時候不容易出錯, 而且如果改動了字典表的值只需要更改對應(yīng)的枚舉類即可.
-
統(tǒng)一格式, 增添功能, 一般來說枚舉中都會需要使用到一些統(tǒng)一的方法, 例如通過value獲取label, 通過label獲取value, 或者多選字段的值轉(zhuǎn)換, 類似于這種統(tǒng)一的方法就可以通過在
IDictItem
接口中增刪方法來調(diào)整整體的功能. -
方便為單個字段添加額外邏輯, 因為使用的是枚舉類, 所以只需要在字段對應(yīng)的枚舉類中添加方法就能很方便的使用和管理字段的處理邏輯.
例如在開發(fā)中難免會遇到對于某個有數(shù)據(jù)字典的字段需要做一些單獨的處理, 而這些處理邏輯直接寫在代碼里或者單獨的類里面都不方便, 遇到這種情況就可以將此種邏輯寫道對應(yīng)的枚舉類中.
接下來說一個簡單的示例: 例如就上面的教師類的Gender字段, 在內(nèi)部使用的數(shù)據(jù)字典是
("1", "男"),("2", "女")
, 但是由于某種情況, 向外部進行推送的時候需要改成("male", "男"),("female ", "女")
的格式, 這樣的話就可以在枚舉中添加兩個方法就可以完方便解決./** * 性別 : {男:1, 女:2} */ enum Gender implements IDictItem { man("1", "男"), woman("2", "女"); Gender(String value, String label) { putItemBean(value, label); } public String innerToOuter(String val) { if (man.isValue(val)) { return "male"; } if (woman.isValue(val)) { return "female"; } throw new RuntimeException("轉(zhuǎn)換出現(xiàn)異常"); } String outerToInner(String val) { if ("male".equals(val)) { return man.value(); } if ("female".equals(val)) { return woman.value(); } throw new RuntimeException("轉(zhuǎn)換出現(xiàn)異常"); } }
source
git
相關(guān)源碼我已放到了github和gitee上管理, 上面有最新的代碼, 以及一些開發(fā)中的功能, 歡迎大家下載查看
github: https://github.com/cosycode/code-dict
gitee: https://gitee.com/cosycode/code-dict`
同時我也將代碼打包成jar, 發(fā)布到 maven 倉庫, 歡迎大家使用
repo
-
Apache Maven
<dependency> <groupId>com.github.cosycode</groupId> <artifactId>code-dict</artifactId> <version>1.1</version> </dependency>
-
gradle
implementation 'com.github.cosycode:code-dict:1.1'