code-dict:巧用枚举让字典表的管理和使用变得简单, 优雅

2023-10-29

巧用枚举类型来管理数据字典

背景

开发 Java 项目时, 数据字典的管理是个令人头痛的问题, 至少对我而言是这样的, 我所在的上一家公司项目里面对于字典表的管理是可以进行配置的, 他们是将字典表统一存放在一个数据库里面进行配置, 然后可以由管理员进行动态的实现字典表的变更.

数据结构表

先来两个数据表(简单一点, 一些非空, 长度什么的就不写了), 两个表都有 genderstate , gender 字典项相同, 但 state 字典项不同

  1. 学生表 Student

    字段名(field) 类型 字典项
    stuNo INTEGER
    name VARCHAR
    gender VARCHAR 性别 : {男, 女}
    state VARCHAR 状态 : {未报到, 在读, 毕业, 结业, 肄业, 退学, 开除}
  2. 教师表 Teacher

    字段名(field) 类型 字典项
    teaNo INTEGER
    name VARCHAR
    gender VARCHAR 性别 : {男, 女}
    state VARCHAR 状态 : {未报到, 在职, 离职, 开除}

使用枚举来管理数据字典

  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. 在数据字段 Gender 中, 通过代码获取文本(eg: 通过代码 1 来获取 这个文本).
  2. 在数据字段 Gender 中, 通过代码获取整个枚举对象(eg: 通过文本 1 来获取MAN这个枚举).
  3. 在数据字段 Gender 中, 判断一个代码是否对应这个枚举(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 是否 和参数中的 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; }
    }
}

看到这里估计就算是没有强迫症的人也觉得枚举真的是太麻烦了, 一般一个普通项目中也都有几十上百个枚举, 一个枚举都这么麻烦, 那么大项目中成百上千个枚举的话还怎么搞啊!
那么有没有什么方法能够将枚举变得简单点呢?

枚举的优化策略

按照上面的写法, 里面的很多方法都是可以相同的, 甚至连 value, 和 label 成员变量都是相同的, 那么像这类重复代码使用继承是最好不过的.
然而枚举中是不能够使用继承的, 至于之后的 jdk 能不能实现枚举继承我们先不讨论, 现在至少 jdk1.8 版本的枚举是不能够使用继承的.

在这里插入图片描述

那么我们还有其他办法吗, 不要着急, 办法肯定有; 让我们一步步分析.

枚举是不能够使用继承的, 但是可以实现接口, 尤其是 1.8 版本之后, 通过接口里面的默认方法, 简直和继承抽象类很相像了有没有.

那么我们可以添加一个接口 IDictItem . 可是问题又来了, 我们该怎么样是的枚举和接口联系起来呢.

首先枚举 Gender 里面的静态方法 public static String getTextByCode(String value) & public static Gender getByCode(String value) , 我们可以将它放在接口里面作为一个通用的静态方法, 而枚举里面的成员函数我们可以将它们变成 IDictItem 中的默认方法.

第一步优化 : 枚举继承接口

  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进行循环比较,获取对应的枚举
                   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 是否 和参数中的 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 方法也都是相同的啊, 能不能把这些代码给消除掉呢,

第二步优化 : 增加 Bean 存枚举值, 使用享元模式存储 Bean

思路

  1. 我仔细考虑了一下, 对于一个有参数的枚举来说, 在初始化的时候类加载器会首先执行枚举项, 也就是调用枚举的构造方法,

  2. Gender 为例, 初始化时, 首先执行 MAN("1", "男") , WOMAN("2", "女") , 调用 Gender(String value, String label) , 将参数 label , 和 value 存至枚举对象的 label , 和 value 成员变量中, 之后我们通过 get 方法获取成员变量 label , 和 value 的值来使用枚举.

  3. 因此如果我们想要消除 label , 和 value 成员变量, 那么必须给他们一个存储的空间来存取它们, 例如可以使用一个 map 来保存它们.

具体实现方案 :

我们先将枚举的参数 label , 和 value 封装成一个普通java对象 DicCodeBean , 然后通过享元模式将这些 DicCodeBean 存放在 DicCodePool , DicCodePool 里面就是一个map, 再添加一个get, 和put 方法.

代码

  1. DicCodeBean : 用来封装参数 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() {}
    
       /**
       * 用于存储字典数据
       */
       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));
       }
    
       /**
       * 获取静态数据
       */
       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);
          }
       }
    

示例

接下来实际演示一下这种方式的优势, 例如上面的两张表, 我们就可以写成下面的代码

  1. 学生字典表

    public class DicStudent {
    
       /**
       * 性别
       */
       enum Gender implements IDictItem {
          MAN("1", "男"), WOMAN("2", "女");
          Gender(String value, String label) {
             putDictItem(value, label);
          }
       }
    
       /**
       * 状态
       */
       enum State implements IDictItem {
          READING("10", "在读"), GRADUATION("20", "毕业"), DEFAMATION("30", "肄业"), WITHDRAWAL("40", "退学"), 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);
          }
       }
    
       /**
       * 状态
       */
       enum State implements IDictItem {
          WORK("10", "在职"), RESIGNED("20", "离职"), EXPELLED("30", "开除");
    
          State(String value, String label) {
             putDictItem(value, label);
          }
       }
    }
    

是不是很简单, 每一张表对应一个枚举管理类, 表中的字典项, 对应类中的一个枚举类, 很方便的将各个枚举分离出来, 而且在使用的时候, 利用IDE工具的提示, 可以非常方便地进行编写, 而且利用枚举里面的方法可以降低很多代码哦.

使用枚举管理数据字典的好处

  1. 使用简单方便, 用到一张表的某个含有数据字典的字段的时候, 能够根据IDE的代码提示轻松获取字段的各个信息.

    enum-merit-1.gif

  2. 防止出错, 易于维护, 因为使用的是枚举, 使用的时候不容易出错, 而且如果改动了字典表的值只需要更改对应的枚举类即可.

  3. 统一格式, 增添功能, 一般来说枚举中都会需要使用到一些统一的方法, 例如通过value获取label, 通过label获取value, 或者多选字段的值转换, 类似于这种统一的方法就可以通过在 IDictItem 接口中增删方法来调整整体的功能.

    enum-merit-3.png

  4. 方便为单个字段添加额外逻辑, 因为使用的是枚举类, 所以只需要在字段对应的枚举类中添加方法就能很方便的使用和管理字段的处理逻辑.

    例如在开发中难免会遇到对于某个有数据字典的字段需要做一些单独的处理, 而这些处理逻辑直接写在代码里或者单独的类里面都不方便, 遇到这种情况就可以将此种逻辑写道对应的枚举类中.

    接下来说一个简单的示例: 例如就上面的教师类的Gender字段, 在内部使用的数据字典是("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("转换出现异常");
       }
    
       String outerToInner(String val) {
          if ("male".equals(val)) {
                return man.value();
          }
          if ("female".equals(val)) {
                return woman.value();
          }
          throw new RuntimeException("转换出现异常");
       }
    }
    

git

相关源码我已放到了github和gitee上管理, 上面有最新的代码, 以及一些开发中的功能, 欢迎大家下载查看

github: https://github.com/cosycode/code-dict
gitee: https://gitee.com/cosycode/code-dict`

同时我也将代码打包成jar, 发布到 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'
    
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

code-dict:巧用枚举让字典表的管理和使用变得简单, 优雅 的相关文章

随机推荐

  • 你需要知道的 7 个 Vue3 技巧

    VNode 钩子 在每个组件或html标签上 我们可以使用一些特殊的 文档没写的 钩子作为事件监听器 这些钩子有 onVnodeBeforeMount onVnodeMounted onVnodeBeforeUpdate onVnodeUp
  • 腾讯计费平台部分布式MySQL数据库TDSQL架构分析

    allowtransparency true frameborder 0 scrolling no src http hits sinajs cn A1 weiboshare html url http 3A 2F 2Fwww csdn n
  • STM32 超声波模块测距

    目录 HC SR04模块 HC SR04超声波测距模块工作原理 HC SR04超声波模块的时序触发图 思路步骤 代码 实验结果 总结 HC SR04模块 HC SR04超声波测距模块工作原理 1 采用IO口TRIG触发测距 给至少10us的
  • Metasploitable安装与介绍

    今天继续给大家介绍渗透测试相关知识 本文主要内容是Metasploitable安装与介绍 免责声明 本文所介绍的内容仅做学习交流使用 严禁利用文中技术进行非法行为 否则造成一切严重后果自负 一 Metasploitable介绍 Metasp
  • ios ffmpeg 保存mp4

    废话不说 直接上代码 header写入成功 void init mp4saving AVFormatContext pFormatCtx videoStreamin int videoStream savenamein NSString s
  • Sonar:VSCode配置SonarLint/SonarLint连接SonarQube

    文章目录 需求描述 准备工作 SonarLint SonarQube版本7 9 SonarQube版本 lt 7 9 其他 需求描述 公司为项目代码配置了Sonar检测 希望在VSCode中开发项目时能够同步远程检测 准备工作 首先要搭建j
  • 如何获取在线零售商的用户评价数据:一个全面的指南和Python代码示例

    目录 网络爬虫的基本原理 Python爬虫库简介 获取在线零售商的用户评价数据 网络爬虫的注意事项
  • kvm和qemu交互处理io流程

    1 IO虚拟化的分类 1 全虚拟化 宿主机截获客户机对I O设备的访问请求 然后通过软件模拟真实的硬件 这种方式对客户机而言非常透明 无需考虑底层硬件的情况 不需要修改操作系统 QEMU模拟I O的情况下 当客户机中的设备驱动程序 devi
  • linux中的解压文件命令有哪些

    解压文件命令有 1 tar 语法 tar xvf 压缩文件名 tar 2 gzip 语法 gunzip 压缩文件名 gz 3 bzip2 语法 bzip2 d 压缩文件名 bz2 4 rar 语法 rar x 压缩文件名 rar linux
  • 大整数相乘的问题

    include
  • AI算力及评估

    AI 算力及评估 准备写一下关于算力相关的文档 后续补全 算力定义 算力单位 FLOPS 每秒浮点运算次数 每秒峰值速度 每秒所执行的浮点运算次数 floating point operations per second 它常被用来估算电脑
  • IDaaS 系统 ArkID 一账通内置插件:图形验证码认证因素的配置流程

    图形验证码认证因素插件功能介绍 图形验证码认证因素插件对用户认证凭证表单进行扩充 插入图形验证码并实现相关验证功能 是 IDaaS 一账通 ArkID 系统内置功能插件之一 注意 图形验证码认证因素不具有认证 注册 修改密码等功能 仅对其他
  • Flash cookie -- 本地共享对象(LOCAL SHARED OBJECTS)

    写道 本地共享对象 有时也称为 Flash cookie 是一些可由您访问的站点在您的计算机上创建的数据文件 共享对象大多数情况下用来增强您浏览 Web 的体验 网站可以在您的计算机上编写 cookie 当您下次访问该网站时 它将加载该 c
  • 29:统计字符数

    29 统计字符数 查看 提交 统计 提问 总时间限制 1000ms 内存限制 65536kB 描述 给定一个由a z这26个字符组成的字符串 统计其中哪个字符出现的次数最多 输入 输入包含一行 一个字符串 长度不超过1000 输出 输出一行
  • Mysql数据库学习&安装

    数据库相关概念 1 数据库 存储数据的仓库 数据时有组织的进行存储 DataBase 简称DB 2 数据库管理系统 管理数据库的大型软件 DataBase Management System 简称DBMS 3 SQL Structures
  • MySQL零基础入门教程!

    手把手教你入门MySQL零基础入门教程 目前MySQL已经成为最为流行的开源关系数据库系统 并且一步一步地占领了原有商业数据库的市场 可以看到Google Facebook Yahoo 网易 久游等大公司都在使用MySQL数据库 甚至将其作
  • Navicat for MySQL 安装教程

    文章目录 一 下载地址 二 安装步骤 1 解压后得到两个应用程序 2 先安装 navicat111 mysql cs x64 exe 3 再安装 PatchNavicat exe 4 安装成功后 会有成功弹框提示 5 运行Navicat 输
  • linux中python安装mysqlclient的注意事项

    在linux中需要先安装python devel和mysql devel 例如在fedora35执行中执行以下命令 sudo dnf install python3 devel sudo dnf install community mysq
  • vue 拖拽【对多个div进行操作】

    一 原理分析 onmousedown事件 var disX ev clientX oDiv offsetLeft var disY ev clientY oDiv offsetTop onmousemove事件 var l ev clien
  • code-dict:巧用枚举让字典表的管理和使用变得简单, 优雅

    巧用枚举类型来管理数据字典 文章目录 巧用枚举类型来管理数据字典 背景 数据结构表 使用枚举来管理数据字典 枚举的增强使用 枚举里加方法 枚举的优化策略 第一步优化 枚举继承接口 第二步优化 增加 Bean 存枚举值 使用享元模式存储 Be