用Java編寫更好的不可變DTO的技巧 - Seb
為了使用來自外部服務的資料,我們通常將JSON有效負載轉換為資料傳輸物件(DTO)。快速處理DTO的程式碼變得很複雜,但是一些技巧可以有所幫助。我們可以編寫易於互動的DTO,使客戶端程式碼更易於編寫和閱讀的DTO。這些技巧一起使用,有助於使其保持簡單。
讓我們從使用JSON的典型方法開始:
{ "name": "Regina", "ingredients": ["Ham", "Mushrooms", "Mozzarella", "Tomato purée"] } |
建立了一個名為的簡單DTO PizzaDto:
import java.util.List; public static class PizzaDto { private String name; private List<String> ingredients; public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getIngredients() { return ingredients; } public void setIngredients(List<String> ingredients) { this.ingredients = ingredients; } } |
PizzaDto是一個普通的Java舊物件:帶有屬性,獲取器,設定器的物件,僅此而已。它反映了JSON結構,因此物件和JSON之間的轉換隻是一種形式。這是Jackson庫的示例:
String json = """ { "name": "Regina", "ingredients": [ "Ham", "Mushrooms", "Mozzarella", "Tomato purée" ] } """; // from JSON to Object PizzaDto dto = new ObjectMapper().readValue(json, PizzaDto.class); // from Object to JSON json = new ObjectMapper().writeValueAsString(dto); |
轉換很簡單。所以有什麼問題?
在現實生活中,DTO可能非常複雜。建立和初始化DTO的程式碼可能非常龐大:有時需要數十行程式碼。有時更多。這是一個問題,因為複雜的程式碼包含更多的錯誤,並且對更改的響應較慢。
我簡化DTO建立的第一步是使用不可變的 DTO:建立後無法修改的DTO。
建立不可變的DTO
當物件在構造後無法更改時,它是不可變的。
讓我們重寫PizzaDto使其不可變:
import java.util.List; public class PizzaDto { private final String name; private final List<String> ingredients; public PizzaDto(String name, List<String> ingredients) { this.name = name; if (ingredients != null) { ingredients = List.copyOf(ingredients); } this.ingredients = ingredients; } public String getName() { return name; } public List<String> getIngredients() { return ingredients; } } |
不可變版本沒有設定器setter。所有屬性均為最終屬性,必須在構造時進行初始化。
Effective Java的作者Joshua Bloch提出了建立不可變類的建議:
“如果您的類具有引用可變物件的任何欄位,請確保該類的客戶端無法獲取對這些物件的引用。” 約書亞·布洛赫(Joshua Bloch)
如果DTO的任何屬性是可變的,則需要製作防禦性副本。使用防禦性副本,可以保護DTO免受外部修改。
好的。現在,我們有了一個不變的DTO。但是,它如何簡化程式碼?
不變性的好處
不變性帶來很多好處,但這是我的最愛:不變變數沒有副作用。
我們來看一個例子。此程式碼段中有一個錯誤:
var pizza = make(); verify(pizza); serve(pizza); |
執行此程式碼後,pizza沒有達到預期的狀態。哪裡引起了問題?
我們將考慮2個答案:首先是一個可變變數,然後是一個不可變變數。
第一個答案,加上可變的比薩餅。pizza由make()建立,但可以在verify()和serve()中進行修改。因此,該錯誤可能來自3條可能中的任何一個。
現在,第二個答案:如果是一個不變的披薩。make()返回一個比薩餅,但verify()並serve()不能對其進行修改。問題只能來自make()。在這裡,查錯範圍要小得多。該錯誤更容易找到。
由於比薩餅是不變的,verify()因此不能僅將其修復。它必須建立並返回一個修改過的披薩,並且必須修改客戶端程式碼:
var pizza = make(); pizza = verify(pizza); serve(pizza); |
在這個新版本中,很明顯會verify()返回一個新的不變披薩。不變性使您的程式碼更加明確。它變得更容易閱讀和發展。
您可能不知道,但是我們已經每天都在使用不可變的物件。java.lang.String,java.math.BigDecimal,java.io.File是不可改變的。不變性還具有許多其他優點。約書亞·布洛赫(Joshua Bloch)在他的《有效Java》中只是建議“最小化可變性”。
不可變的類比可變的類更容易設計,實現和使用。它們不太容易出錯,並且更安全。約書亞·布洛赫(Joshua Bloch)
序列化DTO
Jackson是Java中最常見的JSON庫。當你DTO有getters和setters時, ,Jackson無需任何額外配置即可將物件對映到JSON。但是對於不可變的物體,傑克遜需要一點幫助。它需要知道如何構造物件。
必須使用註釋物件的建構函式@JsonCreator,並使用註釋每個引數。
傑克遜還有另一個好處。如果我們在建構函式中放入一些邏輯,則無論DTO是由應用程式程式碼建立還是由JSON生成,都將始終呼叫該邏輯。
我們可以利用這一點,避免使用null值。我們可以改進建構函式以使用非空值初始化欄位。
/ new import :
// import static org.apache.commons.lang3.ObjectUtils.firstNonNull;
@JsonCreator
public PizzaDto(
@JsonProperty("name") String name,
@JsonProperty("ingredients")
List<String> ingredients) {
this.name = firstNonNull(name, ""); // replace null by empty String
this.ingredients = List.copyOf(
firstNonNull(ingredients, List.of()) // replace null by empty List
);
}
如果我們用“”值替換空null值,則客戶端可以使用DTO屬性,而無需先檢查它是否不為空。另外,它降低了獲得NullPointerExceptions的機會。
有了這個技巧,您可以減少編寫程式碼,並提高魯棒性。我們如何做得更好?
使用Builders建立DTO
構建器提供了一個流暢的API,以促進DTO初始化。
var pizza = new PizzaDto.Builder() .name("Regina") .ingredients("Mozzarella cheese", "Basil leaves", "Olive oil", "Tomato purée") .build(); |
使用複雜的DTO,構建器可以使程式碼更具表現力。這種模式是如此出色.
public static final class Builder { private String name; private List<String> ingredients; public Builder name(String name) { this.name = name; return this; } public Builder ingredients(List<String> ingredients) { this.ingredients = ingredients; return this; } /** * overloads {@link Builder#ingredients(List)} to accept String varargs */ public Builder ingredients(String... ingredients) { return ingredients(List.of(ingredients)); } public PizzaDto build() { return new PizzaDto(name, ingredients); } } |
有些人在編譯時使用Lombok來建立構建器。這使DTO變得簡單。
我更喜歡使用Builder生成器IntelliJ外掛生成生成器程式碼。然後,可以像上一片段一樣新增方法過載。構建器更靈活,客戶端程式碼更精簡。
結論
這些是我用來編寫DTO的主要技巧。一起使用,它們確實可以改善您的程式碼。該程式碼庫更易於閱讀,更易於維護,並且最終更易於與您的團隊共享。
相關文章
- 編寫更好的 Java 單元測試的 7 個技巧Java
- 用jQuery編寫出更好的程式碼jQuery
- 編寫更好的CSSCSS
- 【譯】編寫更好JavaScript條件語句的5個技巧JavaScript
- 「譯」編寫更好的 JavaScript 條件式和匹配條件的技巧JavaScript
- 在Vue.js編寫更好的v-for迴圈的6種技巧Vue.js
- 【譯】如何更好的編寫CSSCSS
- 編寫更好的CSS程式碼CSS
- 如何更好的編寫async函式函式
- 編寫更好的C#程式碼C#
- 編寫更好的jQuery程式碼的建議jQuery
- Java中建立不可變的類Java
- 編寫更好程式碼的 6 個提示
- 如何寫出更好的Java程式碼Java
- [譯] 利用 Immutability(不可變性)編寫更為簡潔高效的程式碼
- 30 多年的軟體經驗,總結出 10 個編寫出更好程式碼的技巧
- 使用正規表示式編寫更好的 SQLSQL
- 使用正規表示式編寫更好的SQLSQL
- Vue3,用組合的方式來編寫更好的程式碼(1/5)Vue
- 編寫更好 Bash 指令碼的 8 個建議指令碼
- Java中的不可變資料結構Java資料結構
- 深入理解Java中的不可變物件Java物件
- Java中如何快捷的建立不可變集合Java
- Java™ 教程(不可變物件)Java物件
- HVM:Rust編寫的比Haskell GHC更好的執行時RustHaskell
- 用C++編寫序號產生器的一點技巧C++
- Vue3,用組合編寫更好的程式碼:動態返回(3/4)Vue
- Vue3,用組合編寫更好的程式碼:靈活的引數(2/5)Vue
- 變分自編碼器(五):VAE + BN = 更好的VAE
- 用Java編寫ASP元件 (轉)Java元件
- oracle sqr編寫技巧Oracle
- CTF中的EXP編寫技巧 zio庫的使用
- 5款工具助你寫出更好的Java程式碼Java
- Java 中的 String 為什麼是不可變的?Java
- Java | DO / DTO / BO / VO的區別Java
- 用Java編寫一個最簡單的桌面程式Java
- 高效的jQuery程式碼編寫技巧總結jQuery
- 編寫優秀程式碼的10個技巧