code-dict:巧用枚舉類型來管理數(shù)據(jù)字典

背景

開發(fā) Java 項目時, 數(shù)據(jù)字典的管理是個令人頭痛的問題, 至少對我而言是這樣的, 我所在的上一家公司項目里面對于字典表的管理是可以進行配置的, 他們是將字典表統(tǒng)一存放在一個數(shù)據(jù)庫里面進行配置, 然后可以由管理員進行動態(tài)的實現(xiàn)字典表的變更.

數(shù)據(jù)結(jié)構(gòu)表

先來兩個數(shù)據(jù)表(簡單一點, 一些非空, 長度什么的就不寫了), 兩個表都有 genderstate , gender 字典項相同, 但 state 字典項不同

  1. 學(xué)生表 Student

    字段名(field) 類型 字典項
    stuNo INTEGER
    name VARCHAR
    gender VARCHAR 性別 : {男, 女}
    state VARCHAR 狀態(tài) : {未報到, 在讀, 畢業(yè), 結(jié)業(yè), 肄業(yè), 退學(xué), 開除}
  2. 教師表 Teacher

    字段名(field) 類型 字典項
    teaNo INTEGER
    name VARCHAR
    gender VARCHAR 性別 : {男, 女}
    state VARCHAR 狀態(tài) : {未報到, 在職, 離職, 開除}

使用枚舉來管理數(shù)據(jù)字典

  1. 以上面的 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, 那么可以在里面增加一個方法

  1. 在數(shù)據(jù)字段 Gender 中, 通過代碼獲取文本(eg: 通過代碼 1 來獲取 這個文本).
  2. 在數(shù)據(jù)字段 Gender 中, 通過代碼獲取整個枚舉對象(eg: 通過文本 1 來獲取MAN這個枚舉).
  3. 在數(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)化 : 枚舉繼承接口

  1. 接口類

       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;
          }
       }
    
  2. 枚舉類

          /**
       * 性別
       */
       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

思路

  1. 我仔細考慮了一下, 對于一個有參數(shù)的枚舉來說, 在初始化的時候類加載器會首先執(zhí)行枚舉項, 也就是調(diào)用枚舉的構(gòu)造方法,

  2. Gender 為例, 初始化時, 首先執(zhí)行 MAN("1", "男") , WOMAN("2", "女") , 調(diào)用 Gender(String value, String label) , 將參數(shù) label , 和 value 存至枚舉對象的 label , 和 value 成員變量中, 之后我們通過 get 方法獲取成員變量 label , 和 value 的值來使用枚舉.

  3. 因此如果我們想要消除 label , 和 value 成員變量, 那么必須給他們一個存儲的空間來存取它們, 例如可以使用一個 map 來保存它們.

具體實現(xiàn)方案 :

我們先將枚舉的參數(shù) label , 和 value 封裝成一個普通java對象 DicCodeBean , 然后通過享元模式將這些 DicCodeBean 存放在 DicCodePool , DicCodePool 里面就是一個map, 再添加一個get, 和put 方法.

代碼

  1. DicCodeBean : 用來封裝參數(shù) labelvalue

    public class DicCodeBean {
    
          public final String value;
    
          public final String label;
    
          public DicCodeBean(String value, String label) {
             this.value = value;
             this.label = label;
          }
    }
    
  2. 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);
       }
    }
    
  3. 那么接下來我們的枚舉類就可以簡化成這樣

       /**
       * 性別
       */
       enum Gender implements IDictItem {
          MAN("1", "男"), WOMAN("2", "女");
          Gender(String value, String label) {
             putDictItem(value, label);
          }
       }
    

示例

接下來實際演示一下這種方式的優(yōu)勢, 例如上面的兩張表, 我們就可以寫成下面的代碼

  1. 學(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);
          }
       }
    }
    
  2. 教師字典表

    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ù)字典的好處

  1. 使用簡單方便, 用到一張表的某個含有數(shù)據(jù)字典的字段的時候, 能夠根據(jù)IDE的代碼提示輕松獲取字段的各個信息.

    enum-merit-1.gif
  2. 防止出錯, 易于維護, 因為使用的是枚舉, 使用的時候不容易出錯, 而且如果改動了字典表的值只需要更改對應(yīng)的枚舉類即可.

  3. 統(tǒng)一格式, 增添功能, 一般來說枚舉中都會需要使用到一些統(tǒng)一的方法, 例如通過value獲取label, 通過label獲取value, 或者多選字段的值轉(zhuǎn)換, 類似于這種統(tǒng)一的方法就可以通過在 IDictItem 接口中增刪方法來調(diào)整整體的功能.

    enum-merit-3.png
  4. 方便為單個字段添加額外邏輯, 因為使用的是枚舉類, 所以只需要在字段對應(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

  1. Apache Maven

    <dependency>
    <groupId>com.github.cosycode</groupId>
    <artifactId>code-dict</artifactId>
    <version>1.1</version>
    </dependency>
    
  2. gradle

    implementation 'com.github.cosycode:code-dict:1.1'
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子擦秽,更是在濱河造成了極大的恐慌,老刑警劉巖嗓袱,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機媳握,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磷脯,“玉大人蛾找,你說我怎么就攤上這事≌眨” “怎么了腋粥?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長架曹。 經(jīng)常有香客問我隘冲,道長,這世上最難降的妖魔是什么绑雄? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任展辞,我火速辦了婚禮,結(jié)果婚禮上万牺,老公的妹妹穿的比我還像新娘罗珍。我一直安慰自己,他們只是感情好脚粟,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布覆旱。 她就那樣靜靜地躺著,像睡著了一般核无。 火紅的嫁衣襯著肌膚如雪扣唱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音噪沙,去河邊找鬼炼彪。 笑死,一個胖子當著我的面吹牛正歼,可吹牛的內(nèi)容都是我干的辐马。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼局义,長吁一口氣:“原來是場噩夢啊……” “哼喜爷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旭咽,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤贞奋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后穷绵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轿塔,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年仲墨,在試婚紗的時候發(fā)現(xiàn)自己被綠了勾缭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡目养,死狀恐怖俩由,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癌蚁,我是刑警寧澤幻梯,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站努释,受9級特大地震影響碘梢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伐蒂,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一煞躬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逸邦,春花似錦恩沛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桥狡,卻和暖如春佛纫,著一層夾襖步出監(jiān)牢的瞬間妓局,已是汗流浹背总放。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工呈宇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人局雄。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓甥啄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親炬搭。 傳聞我的和親對象是個殘疾皇子蜈漓,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容