你见过哪些目瞪口呆的 Java 代码技巧?

  • 2021-12-11
  • Admin

前言:

感谢大家观看本文,希望大家都能有所收获

导语

本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事情。

开发工具

不知道有多少”老”程序员还在使用 Eclipse,这些程序员们要不就是因循守旧,要不就是根本就不知道其他好的开发工具的存在,Eclipse 吃内存卡顿的现象以及各种偶然莫名异常的出现,都告知我们是时候寻找新的开发工具了。

更换 IDE

根本就不想多解释要换什么样的 IDE,如果你想成为一个优秀的 Java 程序员,请更换 IntelliJ IDEA。使用 IDEA 的好处,请搜索谷歌。

别告诉我快捷键不好用

更换 IDE 不在我本文的重点内容中,所以不想用太多的篇幅去写为什么更换IDE。在这里,我只能告诉你,更换 IDE 只为了更好、更快的写好 Java 代码。原因略。

别告诉我快捷键不好用,请尝试新事物。

bean

bean 使我们使用最多的模型之一,我将以大篇幅去讲解 bean,希望读者好好体会。

domain 包名

根据很多 Java 程序员的”经验”来看,一个数据库表则对应着一个 domain 对象,所以很多程序员在写代码时,包名则使用:com.xxx.domain ,这样写好像已经成为了行业的一种约束,数据库映射对象就应该是 domain。但是你错了,domain 是一个领域对象,往往我们再做传统 Java 软件 Web 开发中,这些 domain 都是贫血模型,是没有行为的,或是没有足够的领域模型的行为的,所以,以这个理论来讲,这些 domain 都应该是一个普通的 entity 对象,并非领域对象,所以请把包名改为:com.xxx.entity。

如果你还不理解我说的话,请看一下 Vaughn Vernon 出的一本叫做《IMPLEMENTING DOMAIN-DRIVEN DESIGN》(实现领域驱动设计)这本书,书中讲解了贫血模型与领域模型的区别,相信你会受益匪浅。

DTO

数据传输我们应该使用 DTO 对象作为传输对象,这是我们所约定的,因为很长时间我一直都在做移动端 API 设计的工作,有很多人告诉我,他们认为只有给手机端传输数据的时候(input or output),这些对象成为 DTO 对象。请注意!这种理解是错误的,只要是用于网络传输的对象,我们都认为他们可以当做是 DTO 对象,比如电商平台中,用户进行下单,下单后的数据,订单会发到 OMS 或者 ERP 系统,这些对接的返回值以及入参也叫 DTO 对象。

我们约定某对象如果是 DTO 对象,就将名称改为 XXDTO,比如订单下发OMS:OMSOrderInputDTO。

DTO 转化

正如我们所知,DTO 为系统与外界交互的模型对象,那么肯定会有一个步骤是将 DTO 对象转化为 BO 对象或者是普通的 entity 对象,让 service 层去处理。

场景

比如添加会员操作,由于用于演示,我只考虑用户的一些简单数据,当后台管理员点击添加用户时,只需要传过来用户的姓名和年龄就可以了,后端接受到数据后,将添加创建时间和更新时间和默认密码三个字段,然后保存数据库。

  1. @RequestMapping("/v1/api/user")
  2. @RestController
  3. public class UserApi {
  4. @Autowired
  5. private UserService userService;
  6. @PostMapping
  7. public User addUser(UserInputDTO userInputDTO){
  8. User user = new User();
  9. user.setUsername(userInputDTO.getUsername());
  10. user.setAge(userInputDTO.getAge());
  11. return userService.addUser(user);
  12. }
  13. }复制代码

我们只关注一下上述代码中的转化代码,其他内容请忽略:

  1. User user = new User();
  2. user.setUsername(userInputDTO.getUsername());
  3. user.setAge(userInputDTO.getAge());复制代码

请使用工具

上边的代码,从逻辑上讲,是没有问题的,只是这种写法让我很厌烦,例子中只有两个字段,如果有 20 个字段,我们要如何做呢? 一个一个进行 set 数据吗?当然,如果你这么做了,肯定不会有什么问题,但是,这肯定不是一个最优的做法。

网上有很多工具,支持浅拷贝或深拷贝的 Utils。举个例子,我们可以使用 org.springframework.beans.BeanUtils#copyProperties 对代码进行重构和优化:

  1. @PostMapping
  2. public User addUser(UserInputDTO userInputDTO){
  3. User user = new User();
  4. BeanUtils.copyProperties(userInputDTO,user);
  5. return userService.addUser(user);
  6. }复制代码

BeanUtils.copyProperties 是一个浅拷贝方法,复制属性时,我们只需要把 DTO 对象和要转化的对象两个的属性值设置为一样的名称,并且保证一样的类型就可以了。如果你在做 DTO 转化的时候一直使用 set 进行属性赋值,那么请尝试这种方式简化代码,让代码更加清晰!

转化的语义

上边的转化过程,读者看后肯定觉得优雅很多,但是我们再写 Java 代码时,更多的需要考虑语义的操作,再看上边的代码:

  1. User user = new User();
  2. BeanUtils.copyProperties(userInputDTO,user);复制代码

虽然这段代码很好的简化和优化了代码,但是他的语义是有问题的,我们需要提现一个转化过程才好,所以代码改成如下:

  1. @PostMapping
  2. public User addUser(UserInputDTO userInputDTO){
  3. User user = convertFor(userInputDTO);
  4. return userService.addUser(user);
  5. }
  6. private User convertFor(UserInputDTO userInputDTO){
  7. User user = new User();
  8. BeanUtils.copyProperties(userInputDTO,user);
  9. return user;
  10. }复制代码

这是一个更好的语义写法,虽然他麻烦了些,但是可读性大大增加了,在写代码时,我们应该尽量把语义层次差不多的放到一个方法中,比如:

  1. User user = convertFor(userInputDTO);
  2. return userService.addUser(user);复制代码

这两段代码都没有暴露实现,都是在讲如何在同一个方法中,做一组相同层次的语义操作,而不是暴露具体的实现。

如上所述,是一种重构方式,读者可以参考 Martin Fowler 的《Refactoring Imporving the Design of Existing Code》(重构 改善既有代码的设计) 这本书中的 Extract Method 重构方式。

抽象接口定义

当实际工作中,完成了几个 API 的 DTO 转化时,我们会发现,这样的操作有很多很多,那么应该定义好一个接口,让所有这样的操作都有规则的进行。

如果接口被定义以后,那么 convertFor 这个方法的语义将产生变化,它将是一个实现类。

看一下抽象后的接口:

  1. public interface DTOConvert<S,T> {
  2. T convert(S s);
  3. }复制代码

虽然这个接口很简单,但是这里告诉我们一个事情,要去使用泛型,如果你是一个优秀的 Java 程序员,请为你想做的抽象接口,做好泛型吧。

我们再来看接口实现:

  1. public class UserInputDTOConvert implements DTOConvert {
  2. @Override
  3. public User convert(UserInputDTO userInputDTO) {
  4. User user = new User();
  5. BeanUtils.copyProperties(userInputDTO,user);
  6. return user;
  7. }
  8. }复制代码

我们这样重构后,我们发现现在的代码是如此的简洁,并且那么的规范:

  1. @RequestMapping("/v1/api/user")
  2. @RestController
  3. public class UserApi {
  4. @Autowired
  5. private UserService userService;
  6. @PostMapping
  7. public User addUser(UserInputDTO userInputDTO){
  8. User user = new UserInputDTOConvert().convert(userInputDTO);
  9. return userService.addUser(user);
  10. }
  11. }复制代码

review code

如果你是一个优秀的 Java 程序员,我相信你应该和我一样,已经数次重复 review 过自己的代码很多次了。

我们再看这个保存用户的例子,你将发现,API 中返回值是有些问题的,问题就在于不应该直接返回 User 实体,因为如果这样的话,就暴露了太多实体相关的信息,这样的返回值是不安全的,所以我们更应该返回一个 DTO 对象,我们可称它为 UserOutputDTO:

  1. @PostMapping
  2. public UserOutputDTO addUser(UserInputDTO userInputDTO){
  3. User user = new UserInputDTOConvert().convert(userInputDTO);
  4. User saveUserResult = userService.addUser(user);
  5. UserOutputDTO result = new UserOutDTOConvert().convertToUser(saveUserResult);
  6. return result;
  7. }复制代码

这样你的 API 才更健全。

不知道在看完这段代码之后,读者有是否发现还有其他问题的存在,作为一个优秀的 Java 程序员,请看一下这段我们刚刚抽象完的代码:

User user = new UserInputDTOConvert().convert(userInputDTO);复制代码

你会发现,new 这样一个 DTO 转化对象是没有必要的,而且每一个转化对象都是由在遇到 DTO 转化的时候才会出现,那我们应该考虑一下,是否可以将这个类和 DTO 进行聚合呢,看一下我的聚合结果:

  1. public class UserInputDTO {
  2. private String username;
  3. private int age;
  4. public String getUsername() {
  5. return username;
  6. }
  7. public void setUsername(String username) {
  8. this.username = username;
  9. }
  10. public int getAge() {
  11. return age;
  12. }
  13. public void setAge(int age) {
  14. this.age = age;
  15. }
  16. public User convertToUser(){
  17. UserInputDTOConvert userInputDTOConvert = new UserInputDTOConvert();
  18. User convert = userInputDTOConvert.convert(this);
  19. return convert;
  20. }
  21. private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
  22. @Override
  23. public User convert(UserInputDTO userInputDTO) {
  24. User user = new User();
  25. BeanUtils.copyProperties(userInputDTO,user);
  26. return user;
  27. }
  28. }
  29. }复制代码

然后 API 中的转化则由:

  1. User user = new UserInputDTOConvert().convert(userInputDTO);
  2. User saveUserResult = userService.addUser(user);复制代码

变成了:

  1. User user = userInputDTO.convertToUser();
  2. User saveUserResult = userService.addUser(user);复制代码

我们再 DTO 对象中添加了转化的行为,我相信这样的操作可以让代码的可读性变得更强,并且是符合语义的。

再查工具类

再来看 DTO 内部转化的代码,它实现了我们自己定义的 DTOConvert 接口,但是这样真的就没有问题,不需要再思考了吗?

我觉得并不是,对于 Convert 这种转化语义来讲,很多工具类中都有这样的定义,这中 Convert 并不是业务级别上的接口定义,它只是用于普通 bean 之间转化属性值的普通意义上的接口定义,所以我们应该更多的去读其他含有 Convert 转化语义的代码。

我仔细阅读了一下 GUAVA 的源码,发现了 com.google.common.base.Convert 这样的定义:

  1. public abstract class Converter<A, B> implements Function<A, B> {
  2. protected abstract B doForward(A a);
  3. protected abstract A doBackward(B b);
  4. //其他略
  5. }复制代码

从源码可以了解到,GUAVA 中的 Convert 可以完成正向转化和逆向转化,继续修改我们 DTO 中转化的这段代码:

  1. private static class UserInputDTOConvert implements DTOConvert<UserInputDTO,User> {
  2. @Override
  3. public User convert(UserInputDTO userInputDTO) {
  4. User user = new User();
  5. BeanUtils.copyProperties(userInputDTO,user);
  6. return user;
  7. }
  8. }复制代码

修改后:

  1. private static class UserInputDTOConvert extends Converter, User> {
  2. @Override
  3. protected User doForward(UserInputDTO userInputDTO) {
  4. User user = new User();
  5. BeanUtils.copyProperties(userInputDTO,user);
  6. return user;
  7. }
  8. @Override
  9. protected UserInputDTO doBackward(User user) {
  10. UserInputDTO userInputDTO = new UserInputDTO();
  11. BeanUtils.copyProperties(user,userInputDTO);
  12. return userInputDTO;
  13. }
  14. }复制代码

看了这部分代码以后,你可能会问,那逆向转化会有什么用呢?其实我们有很多小的业务需求中,入参和出参是一样的,那么我们变可以轻松的进行转化,我将上边所提到的 UserInputDTO 和 UserOutputDTO 都转成 UserDTO 展示给大家。

DTO:

  1. public class UserDTO {
  2. private String username;
  3. private int age;
  4. public String getUsername() {
  5. return username;
  6. }
  7. public void setUsername(String username) {
  8. this.username = username;
  9. }
  10. public int getAge() {
  11. return age;
  12. }
  13. public void setAge(int age) {
  14. this.age = age;
  15. }
  16. public User convertToUser(){
  17. UserDTOConvert userDTOConvert = new UserDTOConvert();
  18. User convert = userDTOConvert.convert(this);
  19. return convert;
  20. }
  21. public UserDTO convertFor(User user){
  22. UserDTOConvert userDTOConvert = new UserDTOConvert();
  23. UserDTO convert = userDTOConvert.reverse().convert(user);
  24. return convert;
  25. }
  26. private static class UserDTOConvert extends Converter<UserDTO, User> {
  27. @Override
  28. protected User doForward(UserDTO userDTO) {
  29. User user = new User();
  30. BeanUtils.copyProperties(userDTO,user);
  31. return user;
  32. }
  33. @Override
  34. protected UserDTO doBackward(User user) {
  35. UserDTO userDTO = new UserDTO();
  36. BeanUtils.copyProperties(user,userDTO);
  37. return userDTO;
  38. }
  39. 原文:https://blog.csdn.net/uuuyy_/article/details/121873816

联系站长

QQ:769220720

Copyright © SibooSoft All right reserved 津ICP备19011444号