久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

如何優(yōu)雅的將DTO轉(zhuǎn)化成BO

 WindySky 2017-11-22

本文轉(zhuǎn)載自http://lrwinx.

DTO

數(shù)據(jù)傳輸我們應(yīng)該使用DTO對(duì)象作為傳輸對(duì)象,這是我們所約定的,因?yàn)楹荛L(zhǎng)時(shí)間我一直都在做移動(dòng)端api設(shè)計(jì)的工作,,有很多人告訴我,,他們認(rèn)為只有給手機(jī)端傳輸數(shù)據(jù)的時(shí)候(input or output),,這些對(duì)象成為DTO對(duì)象,。請(qǐng)注意!這種理解是錯(cuò)誤的,,只要是用于網(wǎng)絡(luò)傳輸?shù)膶?duì)象,,我們都認(rèn)為他們可以當(dāng)做是DTO對(duì)象,比如電商平臺(tái)中,,用戶進(jìn)行下單,,下單后的數(shù)據(jù),訂單會(huì)發(fā)到OMS 或者 ERP系統(tǒng),,這些對(duì)接的返回值以及入?yún)⒁步蠨TO對(duì)象。

我們約定某對(duì)象如果是DTO對(duì)象,,就將名稱改為XXDTO,比如訂單下發(fā)OMS:OMSOrderInputDTO,。

DTO轉(zhuǎn)化

正如我們所知,DTO為系統(tǒng)與外界交互的模型對(duì)象,,那么肯定會(huì)有一個(gè)步驟是將DTO對(duì)象轉(zhuǎn)化為BO對(duì)象或者是普通的entity對(duì)象,,讓service層去處理。

場(chǎng)景

比如添加會(huì)員操作,,由于用于演示,,我只考慮用戶的一些簡(jiǎn)單數(shù)據(jù),當(dāng)后臺(tái)管理員點(diǎn)擊添加用戶時(shí),,只需要傳過來(lái)用戶的姓名和年齡就可以了,,后端接受到數(shù)據(jù)后,將添加創(chuàng)建時(shí)間和更新時(shí)間和默認(rèn)密碼三個(gè)字段,然后保存數(shù)據(jù)庫(kù),。

  1. @RequestMapping("/v1/api/user")  
  2. @RestController  
  3. public class UserApi {  
  4.   
  5.     @Autowired  
  6.     private UserService userService;  
  7.   
  8.     @PostMapping  
  9.     public User addUser(UserInputDTO userInputDTO){  
  10.         User user = new User();  
  11.         user.setUsername(userInputDTO.getUsername());  
  12.         user.setAge(userInputDTO.getAge());  
  13.   
  14.         return userService.addUser(user);  
  15.     }  
  16. }  
我們只關(guān)注一下上述代碼中的轉(zhuǎn)化代碼,,其他內(nèi)容請(qǐng)忽略:
  1. User user = new User();  
  2. user.setUsername(userInputDTO.getUsername());  
  3. user.setAge(userInputDTO.getAge());  

請(qǐng)使用工具

上邊的代碼,從邏輯上講,,是沒有問題的,,只是這種寫法讓我很厭煩,例子中只有兩個(gè)字段,,如果有20個(gè)字段,,我們要如何做呢? 一個(gè)一個(gè)進(jìn)行set數(shù)據(jù)嗎,?當(dāng)然,,如果你這么做了,肯定不會(huì)有什么問題,,但是,,這肯定不是一個(gè)最優(yōu)的做法。

網(wǎng)上有很多工具,,支持淺拷貝或深拷貝的Utils. 舉個(gè)例子,,我們可以使用org.springframework.beans.BeanUtils#copyProperties對(duì)代碼進(jìn)行重構(gòu)和優(yōu)化:

  1. @PostMapping  
  2. public User addUser(UserInputDTO userInputDTO){  
  3.     User user = new User();  
  4.     BeanUtils.copyProperties(userInputDTO,user);  
  5.   
  6.     return userService.addUser(user);  
  7. }  
BeanUtils.copyProperties是一個(gè)淺拷貝方法,復(fù)制屬性時(shí),,我們只需要把DTO對(duì)象和要轉(zhuǎn)化的對(duì)象兩個(gè)的屬性值設(shè)置為一樣的名稱,,并且保證一樣的類型就可以了。如果你在做DTO轉(zhuǎn)化的時(shí)候一直使用set進(jìn)行屬性賦值,,那么請(qǐng)嘗試這種方式簡(jiǎn)化代碼,,讓代碼更加清晰!

轉(zhuǎn)化的語(yǔ)義

上邊的轉(zhuǎn)化過程,讀者看后肯定覺得優(yōu)雅很多,,但是我們?cè)賹慾ava代碼時(shí),,更多的需要考慮語(yǔ)義的操作,再看上邊的代碼:

  1. User user = new User();  
  2. BeanUtils.copyProperties(userInputDTO,user);  
雖然這段代碼很好的簡(jiǎn)化和優(yōu)化了代碼,,但是他的語(yǔ)義是有問題的,,我們需要提現(xiàn)一個(gè)轉(zhuǎn)化過程才好,所以代碼改成如下:
  1. @PostMapping  
  2.  public User addUser(UserInputDTO userInputDTO){  
  3.          User user = convertFor(userInputDTO);  
  4.   
  5.          return userService.addUser(user);  
  6.  }  
  7.   
  8.  private User convertFor(UserInputDTO userInputDTO){  
  9.   
  10.          User user = new User();  
  11.          BeanUtils.copyProperties(userInputDTO,user);  
  12.          return user;  
  13.  }  
這是一個(gè)更好的語(yǔ)義寫法,雖然他麻煩了些,,但是可讀性大大增加了,,在寫代碼時(shí),我們應(yīng)該盡量把語(yǔ)義層次差不多的放到一個(gè)方法中,,比如:
  1. User user = convertFor(userInputDTO);  
  2. return userService.addUser(user);  

這兩段代碼都沒有暴露實(shí)現(xiàn),,都是在講如何在同一個(gè)方法中,做一組相同層次的語(yǔ)義操作,,而不是暴露具體的實(shí)現(xiàn),。如上所述,是一種重構(gòu)方式,讀者可以參考Martin Fowler的《Refactoring Imporving the Design of Existing Code》(重構(gòu) 改善既有代碼的設(shè)計(jì)) 這本書中的Extract Method重構(gòu)方式,。

抽象接口定義

當(dāng)實(shí)際工作中,,完成了幾個(gè)api的DTO轉(zhuǎn)化時(shí),我們會(huì)發(fā)現(xiàn),,這樣的操作有很多很多,,那么應(yīng)該定義好一個(gè)接口,讓所有這樣的操作都有規(guī)則的進(jìn)行,。
如果接口被定義以后,,那么convertFor這個(gè)方法的語(yǔ)義將產(chǎn)生變化,他將是一個(gè)實(shí)現(xiàn)類,。

看一下抽象后的接口:

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

雖然這個(gè)接口很簡(jiǎn)單,,但是這里告訴我們一個(gè)事情,要去使用泛型,,如果你是一個(gè)優(yōu)秀的java程序員,,請(qǐng)為你想做的抽象接口,做好泛型吧,。我們?cè)賮?lái)看接口實(shí)現(xiàn):

  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. }  
我們這樣重構(gòu)后,,我們發(fā)現(xiàn)現(xiàn)在的代碼是如此的簡(jiǎn)潔,并且那么的規(guī)范:
  1. @RequestMapping("/v1/api/user")  
  2. @RestController  
  3. public class UserApi {  
  4.   
  5.     @Autowired  
  6.     private UserService userService;  
  7.   
  8.     @PostMapping  
  9.     public User addUser(UserInputDTO userInputDTO){  
  10.         User user = new UserInputDTOConvert().convert(userInputDTO);  
  11.   
  12.         return userService.addUser(user);  
  13.     }  
  14. }  

review code

如果你是一個(gè)優(yōu)秀的java程序員,,我相信你應(yīng)該和我一樣,,已經(jīng)數(shù)次重復(fù)review過自己的代碼很多次了。
我們?cè)倏催@個(gè)保存用戶的例子,,你將發(fā)現(xiàn),,api中返回值是有些問題的,問題就在于不應(yīng)該直接返回User實(shí)體,,因?yàn)槿绻@樣的話,,就暴露了太多實(shí)體相關(guān)的信息,這樣的返回值是不安全的,,所以我們更應(yīng)該返回一個(gè)DTO對(duì)象,,我們可稱它為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才更健全。不知道在看完這段代碼之后,,讀者有是否發(fā)現(xiàn)還有其他問題的存在,作為一個(gè)優(yōu)秀的java程序員,,請(qǐng)看一下這段我們剛剛抽象完的代碼:

User user = new UserInputDTOConvert().convert(userInputDTO);

你會(huì)發(fā)現(xiàn),,new這樣一個(gè)DTO轉(zhuǎn)化對(duì)象是沒有必要的,而且每一個(gè)轉(zhuǎn)化對(duì)象都是由在遇到DTO轉(zhuǎn)化的時(shí)候才會(huì)出現(xiàn),,那我們應(yīng)該考慮一下,,是否可以將這個(gè)類和DTO進(jìn)行聚合呢,看一下我的聚合結(jié)果:

public class UserInputDTO {
private String username;
private int age;

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

然后api中的轉(zhuǎn)化則由:
User user = new UserInputDTOConvert().convert(userInputDTO);
User saveUserResult = userService.addUser(user);

變成了:
User user = userInputDTO.convertToUser();
User saveUserResult = userService.addUser(user);

我們?cè)貲TO對(duì)象中添加了轉(zhuǎn)化的行為,我相信這樣的操作可以讓代碼的可讀性變得更強(qiáng),,并且是符合語(yǔ)義的,。

再查工具類

再來(lái)看DTO內(nèi)部轉(zhuǎn)化的代碼,它實(shí)現(xiàn)了我們自己定義的DTOConvert接口,,但是這樣真的就沒有問題,,不需要再思考了嗎?
我覺得并不是,,對(duì)于Convert這種轉(zhuǎn)化語(yǔ)義來(lái)講,,很多工具類中都有這樣的定義,這中Convert并不是業(yè)務(wù)級(jí)別上的接口定義,,它只是用于普通bean之間轉(zhuǎn)化屬性值的普通意義上的接口定義,,所以我們應(yīng)該更多的去讀其他含有Convert轉(zhuǎn)化語(yǔ)義的代碼。
我仔細(xì)閱讀了一下GUAVA的源碼,,發(fā)現(xiàn)了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可以完成正向轉(zhuǎn)化和逆向轉(zhuǎn)化,繼續(xù)修改我們DTO中轉(zhuǎn)化的這段代碼:
  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<UserInputDTO, User> {  
  2.          @Override  
  3.          protected User doForward(UserInputDTO userInputDTO) {  
  4.                  User user = new User();  
  5.                  BeanUtils.copyProperties(userInputDTO,user);  
  6.                  return user;  
  7.          }  
  8.   
  9.          @Override  
  10.          protected UserInputDTO doBackward(User user) {  
  11.                  UserInputDTO userInputDTO = new UserInputDTO();  
  12.                  BeanUtils.copyProperties(user,userInputDTO);  
  13.                  return userInputDTO;  
  14.          }  
  15.  }  

看了這部分代碼以后,,你可能會(huì)問,,那逆向轉(zhuǎn)化會(huì)有什么用呢?其實(shí)我們有很多小的業(yè)務(wù)需求中,,入?yún)⒑统鰠⑹且粯拥?,那么我們變可以輕松的進(jìn)行轉(zhuǎn)化,我將上邊所提到的UserInputDTO和UserOutputDTO都轉(zhuǎn)成UserDTO展示給大家:

DTO:

  1. public class UserDTO {  
  2.     private String username;  
  3.     private int age;  
  4.   
  5.     public String getUsername() {  
  6.             return username;  
  7.     }  
  8.   
  9.     public void setUsername(String username) {  
  10.             this.username = username;  
  11.     }  
  12.   
  13.     public int getAge() {  
  14.             return age;  
  15.     }  
  16.   
  17.     public void setAge(int age) {  
  18.             this.age = age;  
  19.     }  
  20.   
  21.   
  22.     public User convertToUser(){  
  23.             UserDTOConvert userDTOConvert = new UserDTOConvert();  
  24.             User convert = userDTOConvert.convert(this);  
  25.             return convert;  
  26.     }  
  27.   
  28.     public UserDTO convertFor(User user){  
  29.             UserDTOConvert userDTOConvert = new UserDTOConvert();  
  30.             UserDTO convert = userDTOConvert.reverse().convert(user);  
  31.             return convert;  
  32.     }  
  33.   
  34.     private static class UserDTOConvert extends Converter<UserDTO, User> {  
  35.             @Override  
  36.             protected User doForward(UserDTO userDTO) {  
  37.                     User user = new User();  
  38.                     BeanUtils.copyProperties(userDTO,user);  
  39.                     return user;  
  40.             }  
  41.   
  42.             @Override  
  43.             protected UserDTO doBackward(User user) {  
  44.                     UserDTO userDTO = new UserDTO();  
  45.                     BeanUtils.copyProperties(user,userDTO);  
  46.                     return userDTO;  
  47.             }  
  48.     }  
  49.   
  50. }  
api:
  1. @PostMapping  
  2.  public UserDTO addUser(UserDTO userDTO){  
  3.          User user =  userDTO.convertToUser();  
  4.          User saveResultUser = userService.addUser(user);  
  5.          UserDTO result = userDTO.convertFor(saveResultUser);  
  6.          return result;  
  7.  }  
當(dāng)然,,上述只是表明了轉(zhuǎn)化方向的正向或逆向,,很多業(yè)務(wù)需求的出參和入?yún)⒌腄TO對(duì)象是不同的,那么你需要更明顯的告訴程序:逆向是無(wú)法調(diào)用的:
  1. private static class UserDTOConvert extends Converter<UserDTO, User> {  
  2.          @Override  
  3.          protected User doForward(UserDTO userDTO) {  
  4.                  User user = new User();  
  5.                  BeanUtils.copyProperties(userDTO,user);  
  6.                  return user;  
  7.          }  
  8.   
  9.          @Override  
  10.          protected UserDTO doBackward(User user) {  
  11.                  throw new AssertionError("不支持逆向轉(zhuǎn)化方法!");  
  12.          }  
  13.  }  

看一下doBackward方法,,直接拋出了一個(gè)斷言異常,,而不是業(yè)務(wù)異常,這段代碼告訴代碼的調(diào)用者,,這個(gè)方法不是準(zhǔn)你調(diào)用的,,如果你調(diào)用,我就”斷言”你調(diào)用錯(cuò)誤了,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多