找回密码
 立即注册
首页 业界区 安全 如你所期,DataIdNameVO 来了!

如你所期,DataIdNameVO 来了!

静轾 2025-5-30 10:58:54
0、背景/解决的问题

anyway,我们的应用系统里有许多仅涉及到数据的id和name的地方。
例如:Web服务端给前端页面的企业检索下拉列表所提供的数据接口,会返回一个包含 enterpriseId、enterpriseName的集合。
再例如:销管(sale)会提供一个rpc接口,返回一个包含saleId、saleName的集合,供WebController接口使用,或者供那些需要saleId、saleName数据的程序使用。
再例如:程序内会有这样的方法,saveByOrderDetailWhenPaySuccess(orderDetail, invoiceClassIdAndName),第二个参数是一组id和name。
除了上面提到的 enterpriseId & enterpriseName、saleId & saleName,我们系统中还包括了 levyId & levyName、providerId & providerName、agentId & agentName,等等等等。用一个词“不胜枚举”来形容,再恰当不过。不仅如此,单就 enterpriseId & enterpriseName 来说,系统里就有不少。
那么,我们能做什么呢?————DataIdNameVO 诞生!
DataIdNameVO,顾名思义,只包含两个属性:id 和 name。
接下来,重点讲解对 DataIdNameVO 的设计。
1、DataIdNameVO 结构

如前文所述,DataIdNameVO 职责极其单一,两袖清风,只包含两个属性:id 和 name。
意味着,我们把 enterpriseId & enterpriseName、saleId & saleName、levyId & levyName、providerId & providerName、agentId & agentName 抽象出来一个公共的 id/name 对儿。
1.1、DataIdNameVO#id 的类型

DataIdNameVO#name 自不必说,是个 String。
DataIdNameVO#id 呢? 通常情况下,我们会将 DataIdNameVO 定义为泛型T, 其中,id的类型是T。这样设计,可以满足所有的场景。
不过,我在定义时做了取舍。一来,泛型对于个别初级同学来说会降低易读性,二来,结合我们系统的实际,我们系统绝大多数数据的id是Long,最终,我选择将id定义为Long。
BTW,有同学将name设计成泛型,当属于过度设计。
2、关于 DataIdNameVO 的实例化

常规的路子,我们通过 new 的方式来实例化 DataIdNameVO 对象。
  1. DataIdNameVO vo = new DataIdNameVO();
  2. vo.setId(xxx);
  3. vo.setName(xxx);
  4. DataIdNameVO 暴露默认无参的 public 构造器。
复制代码
我摈弃了这个方式。
我们应该尽量减少业务代码的工作量。下面1行实例化的方式,比上面3行要好。
  1. DataIdNameVO vo = new DataIdNameVO(xxx, xxx);
复制代码
这时,DataIdNameVO 需要暴露全参构造器。同时,屏蔽掉field的setter。
我摒弃了这个方式。
Java语言的理念里,或者Spring的理念里,它们并不首推利用 new 的方式来实例化对象。我们从hutool、lombok、apache-common等开源组件也可见一斑。
不用new 的方式,自然是builder模式,或通过静态工厂方法的方式,来封装对象的创建逻辑。
  1. DataIdNameVO vo = DataIdNameVO.build(xxx, xxx);
复制代码
按照这样的思路,我在 DataIdNameVO  里定义一个静态 build 方法。
  1. public static DataIdNameVO build(Long id, String name) {
  2.     return new DataIdNameVO(id, name);
  3. }
  4. public DataIdNameVO(Long id, String name) {
  5.     this.id = id;
  6.     this.name = name;
  7. }
复制代码
为什么保留了 全参构造器?
这是因为某些情况下,例如序列化、例如MapStruct等bean转换,可能需要它。
我推荐开发者使用 build 来创建 DataIdNameVO 对象。如何让开发者知道呢?---->javadoc 可以派上用场。
3、DataIdNameVO 实例化:进阶设计

上面的设计,局限于DataIdNameVO 本身。
站在使用者的角度,我们有必要对 实例化 DataIdNameVO 再做一番改造。
业务程序里,会怎么使用 DataIdNameVO 呢?
  1. InvoiceCategory entity = InvoiceCategory 实例;
  2. DataIdNameVO invoiceCategoryIdName = DataIdNameVO.build(entity.getId(), entity.getCategoryName());
  3. invoiceOrderManager.saveByOrderDetailWhenPaySuccess(orderDetail, invoiceCategoryIdName);
复制代码
从这个使用方式来看, DataIdNameVO 不需要做什么了。
我们再来看下面的使用。
  1. List<EnterpriseVO> list = EnterpriseVO 实例集合;
  2. List<DataIdNameVO> dataIdNameVOList =new ArrayList<>();
  3. for (EnterpriseVO enterpriseVO : list){
  4.     dataIdNameVOList.add( DataIdNameVO.build(enterpriseVO.getEnterpriseId(), enterpriseVO.getEnterpriseName()));
  5. }
  6. ...
复制代码
从这个使用方式来看, DataIdNameVO 能做什么?
系统里如果存在大量的这种for循环来构建 DataIdNameVO集合,那么,DataIdNameVO 是可以做点事情的。
上面for循环代码,如果改成下面这样,是不是优雅?
  1. List<EnterpriseVO> list = EnterpriseVO 实例集合;
  2. List<DataIdNameVO> dataIdNameVOList = DataIdNameVO.buildList(list, EnterpriseVO::getEnterpriseId, EnterpriseVO::getEnterpriseName);
  3. ...
复制代码
是的!
于是,DataIdNameVO 有了下面的静态成员,buildList。该方法将一个包含任意 POJO 对象的列表转换为 DataIdNameVO 类型的列表,通过传入的 Function 接口可以灵活地从 POJO 对象中提取所需的 id 和 name。
  1. public static <POJO> List<DataIdNameVO> buildList(List<POJO> list, Function<POJO, Long> id, Function<POJO, String> name) {
  2.     if (list == null) return null;
  3.     List<DataIdNameVO> result = new ArrayList<>(list.size());
  4.     for (POJO obj : list) {
  5.         result.add(build(id.apply(obj), name.apply(obj)));
  6.     }
  7.     return result;
  8. }
复制代码
4、DataIdNameVO 全貌
  1. import lombok.Getter;
  2. import lombok.ToString;
  3. import java.io.Serializable;
  4. import java.util.List;
  5. import java.util.function.Function;
  6. import java.util.stream.Collectors;
  7. /**
  8. * 见名知意,只包含 数据id/数据name 的ValueObject
  9. * <p>
  10. * eg. enterpriseId/enterpriseName, levyId/levyName, etc.
  11. *
  12. * @author zhangguozhan
  13. * 2024-04-11
  14. */
  15. @Getter
  16. @ToString
  17. public class DataIdNameVO implements Serializable {
  18.     /**
  19.      * 数据的id
  20.      */
  21.     private Long id;
  22.     /**
  23.      * 数据的name
  24.      */
  25.     private String name;
  26.     /**
  27.      * @deprecated :推荐使用 {@link #build,#buildList} 来构造对象
  28.      */
  29.     public DataIdNameVO(Long id, String name) {
  30.         this.id = id;
  31.         this.name = name;
  32.     }
  33.     public static DataIdNameVO build(Long id, String name) {
  34.         return new DataIdNameVO(id, name);
  35.     }
  36.     public static <POJO> List<DataIdNameVO> buildList(List<POJO> list, Function<POJO, Long> id, Function<POJO, String> name) {
  37.         if (list == null) return null;
  38.         return list.stream()
  39.                 .map(obj -> build(id.apply(obj), name.apply(obj)))
  40.                 .collect(Collectors.toList());
  41.     }
  42. }
复制代码
总结

小改动,大收益!
我们通过分析我们复杂的企业系统,抽象出来这个 DataIdNameVO,很大程序上可以降低程序的复杂度,增强代码的可读性,并能提高开发效率。
站在宏观的角度,利用OOP设计思想,来不断重构、调优系统,使之可持续发展。当然,这首先需要我们具备这样的能力 和 意识!
/豆包点评/

DataIdNameVO 类在程序设计方面具有较高的质量,通过简洁的代码、良好的注释、合理的设计模式和性能优化,提高了代码的可读性、可维护性、灵活性和性能。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册