经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
做一个不复制粘贴的程序员[1]: 使用模板方法模式(2)- 对象更新比较器实例
来源:cnblogs  作者:thinkam  时间:2019/7/29 9:11:19  对本文有异议

在进入正题之前,说一些废话,谈谈对于我的前一篇文章被移出博客园首页的想法。不谈我对于其他首页文章的看法,光从我自身找找原因。下面分析下可能的原因:

  1. 篇幅太短:我觉得篇幅不能决定文章的质量,要说清楚一个问题,肯定字数越少越好
  2. 代码过多,文字太少:Talk is cheap. Show me the code. 我觉得code比talk更有说服力,而且大多数程序员相对更喜欢看代码。我觉得我的代码说的比我文字说的好(相对而言,我没说我代码写的好 : ) )
  3. 质量不行:只有我觉得能给大家启发的我才会选择发布到首页。上篇文章的例子是我实际工作中遇到的问题,思考出来并且经过时间、实践检验的东西

不给自己找理由,我承认自己水平有限、能力不足,希望自己以后努力能达到要求,欢迎大家在评论区和我交流。

在前一篇文章中,我已经用分页查询的实例,说明了如何用模板方法模式去消除代码重复。那个例子相对比较简单,下面分享一个稍微难一点的例子,加深大家的理解。

一、场景描述

说一个我工作中遇到的场景,有一个消息队列(MQ)监听上游系统推送过来的消息,只对接数据库表中的几个字段,大概流程如下:

  1. 先根据唯一键去查询数据库中是否存在,如果不存在则插入一条新记录
  2. 如果存在,则对比上游推送的对象和数据库查出来的对象,比较对接的那些字段是否相同
  3. 如果都相同,就什么都不操作。如果有不同的,就用那不同的字段去更新数据库中的记录

二、蹩脚的方法

假设和上游系统对接的是用户模块,用户表中有id、name、age、sex四个字段,id是唯一键,现在只要对接id、name、age四个字段。

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class User {
  5. private Integer id;
  6. private String name;
  7. private Integer age;
  8. private Integer sex;
  9. }
  1. public class UserDAO {
  2. private User oneUser = new User(1, "u1", 18, 1);
  3. public User getById(Integer id) {
  4. if (id != 1) {
  5. return null;
  6. }
  7. return oneUser;
  8. }
  9. public void updateById(User user) {
  10. Integer id = user.getId();
  11. if (id == null || id != 1) {
  12. return;
  13. }
  14. if (user.getName() != null) {
  15. oneUser.setName(user.getName());
  16. }
  17. if (user.getAge() != null) {
  18. oneUser.setAge(user.getAge());
  19. }
  20. if (user.getSex() != null) {
  21. oneUser.setSex(user.getSex());
  22. }
  23. }
  24. }
  1. /**
  2. * 对比两个对象,获取用于更新的对象
  3. *
  4. * @param toUpdate 要更新的对象
  5. * @param original 原来的对象
  6. * @return 用于更新的对象。如果要比较的字段都一样则返回null
  7. */
  8. private User getUserUpdate(User toUpdate, User original) {
  9. User updateUser = new User();
  10. if (!original.getName().equals(toUpdate.getName())) {
  11. updateUser.setName(toUpdate.getName());
  12. }
  13. if (!original.getAge().equals(toUpdate.getAge())) {
  14. updateUser.setAge(toUpdate.getAge());
  15. }
  16. if (Stream.of(updateUser.getName(), updateUser.getAge())
  17. .allMatch(Objects::isNull)) {
  18. // 没有更新
  19. return null;
  20. }
  21. return updateUser;
  22. }

MQ监听的方法

  1. // MQ监听方法接收到上游系统推送过来的一条记录,只对接id、name、age字段。id是唯一键(实际中一般不会是id)
  2. User toUpdate = new User(1, "uu1", 20, null);
  3. System.out.println("toUpdate user: " + toUpdate);
  4. // 根据唯一键键去数据库查询查询查询
  5. User original = userDAO.getById(toUpdate.getId());
  6. System.out.println("original user: " + original);
  7. // 如果查到则用上游系统推送的记录去更新这条记录,如果没查到则插入一条新记录
  8. if (original != null) {
  9. // 对比两个对象,获取用于更新的对象
  10. User updateUser = getUserUpdate(toUpdate, original);
  11. // 如果两对象要比较的字段都一样就不操作,否则更新不同的字段
  12. if (updateUser != null) {
  13. // 设置主键id
  14. updateUser.setId(toUpdate.getId());
  15. System.out.println("update user: " + updateUser);
  16. // 根据主键id去更新
  17. userDAO.updateById(updateUser);
  18. System.out.println("updated user: " + userDAO.getById(toUpdate.getId()));
  19. }
  20. } else {
  21. // 插入一条新记录
  22. }

运行结果:

  1. toUpdate user: User(id=1, name=uu1, age=20, sex=null)
  2. original user: User(id=1, name=u1, age=18, sex=1)
  3. update user: User(id=1, name=uu1, age=20, sex=null)
  4. updated user: User(id=1, name=uu1, age=20, sex=1)

分析下上述代码,核心是getUserUpdate方法,如果实际中对接的字段有很多,那么这个方法中的代码很容易出错。这个方法看起来重复的代码是:先比较两对象的同一字段是否相等,如果相等则设值。能不能把这块代码提炼出一个方法来,请思考一会,然后看下面的章节。

三、模板方法

  1. /**
  2. * 对象更新比较器
  3. *
  4. * @param <T> 待比较的对象类型
  5. */
  6. public final class UpdateDiffer<T> {
  7. /**
  8. * 原来的对象
  9. */
  10. private final T original;
  11. /**
  12. * 要更新的对象
  13. */
  14. private final T toUpdate;
  15. /**
  16. * ”原来的对象“和“要更新的对象”比较出来用于更新的对象
  17. */
  18. private final T difference;
  19. /**
  20. * 需要比较的字段的get方法
  21. */
  22. private final List<Function<T, ?>> getMethodList;
  23. /**
  24. * Initializes a newly created UpdateDiffer object
  25. *
  26. * @param original 原来的对象
  27. * @param toUpdate 要更新的对象
  28. * @param tConstructor T类型对象构造方法
  29. */
  30. public UpdateDiffer(T original, T toUpdate, Supplier<T> tConstructor) {
  31. Objects.requireNonNull(original);
  32. Objects.requireNonNull(toUpdate);
  33. Objects.requireNonNull(tConstructor);
  34. this.original = original;
  35. this.toUpdate = toUpdate;
  36. this.difference = tConstructor.get();
  37. getMethodList = new ArrayList<>();
  38. }
  39. /**
  40. * 比较字段是否相同,如果不同,把要更新的对象字段值设置到difference对象里
  41. *
  42. * @param getMethod get方法
  43. * @param setMethod set方法
  44. * @param <R> get方法的返回值类型/set方法参数类型
  45. * @return this
  46. */
  47. public <R> UpdateDiffer<T> diffing(Function<T, R> getMethod, BiConsumer<T, R> setMethod) {
  48. Objects.requireNonNull(getMethod);
  49. Objects.requireNonNull(setMethod);
  50. R toUpdateValue = getMethod.apply(toUpdate);
  51. R originalValue = getMethod.apply(original);
  52. Objects.requireNonNull(originalValue, "数据库中的字段不应该为null");
  53. if (!originalValue.equals(toUpdateValue)) {
  54. setMethod.accept(difference, toUpdateValue);
  55. }
  56. // 保存已经调用的get方法
  57. getMethodList.add(getMethod);
  58. return this;
  59. }
  60. /**
  61. * 获取”原来的对象“和“要更新的对象”比较出来的对象,用于去数据库更新(更新前还要再设置id等字段)。
  62. *
  63. * @return ”原来的对象“和“要更新的对象”比较出来用于更新的对象。如果“原来的对象”和“要更新的对象”中所有要比较的字段都相同,返回null
  64. */
  65. public T diff() {
  66. // 如果difference对象中所有要比较的字段都为null
  67. if (
  68. getMethodList.stream()
  69. .map(getFunction -> getFunction.apply(difference))
  70. .allMatch(Objects::isNull)
  71. ) {
  72. return null;
  73. }
  74. return this.difference;
  75. }
  76. }

用以下的代码替换上一节中的getUserUpdate方法

  1. User updateUser = new UpdateDiffer<>(original, toUpdate, User::new)
  2. .diffing(User::getName, User::setName)
  3. .diffing(User::getAge, User::setAge)
  4. .diff();

运行结果和上一节的结果一样。

是不是觉得代码比之前的清楚了很多,而且不容易出错。代码没啥解释的,我是由JDK中的Comparator启发想出来的,不明白的可以先看看Comparator的用法。

四、结语

模板模式至此就介绍完了,大家的"CTRL"、"C"、"V"键会不会因此增加几年寿命 : )


回到本系列的目录

原文链接:http://www.cnblogs.com/thinkam/p/11261544.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号