如何使用充血模型實現防彈程式碼 - DZone Java
瞭解有關在Java應用程式中透過使用充血模型+構建器等設計器模式設計防彈程式碼的方法。
毫無疑問,優秀的編碼實踐帶來了諸多好處,例如干淨的程式碼,易於維護以及流暢的API。但是,最佳實踐是否有助於資料完整性?
本貼主要涉及新的儲存技術,例如NoSQL資料庫,它們沒有開發人員在使用SQL模式時通常會有的原生驗證。
乾淨程式碼是一個好主題 它是將物件行為公開和資料隱藏,這與結構化程式設計不同,這篇文章目的是解釋使用充血模型而不是失血模型獲得資料完整性和防彈bulletproof程式碼的好處。
需求用例
這篇文章將建立一個系統,將足球運動員分成一個團隊; 該系統的規則是:
- 玩家的名字是必需的
- 所有球員必須有一個位置(守門員,前鋒,後衛和中場)。
- 球員在球隊中進行的目標計數器
- 聯絡電子郵件
- 一個團隊有球員,並根據需要命名
- 一支球隊無法處理超過二十名球員
根據收集的資訊,有第一個草案程式碼版本:
import java.math.BigDecimal; public class Player { String name; Integer start; Integer end; String email; String position; Integer gols; BigDecimal salary; } public class Team { String name; List<Player> players; } |
這裡球員只能有一個固定的位置,需要重構,我們將使用 列舉替代String型別的位置position。
public enum Position { GOALKEEPER, DEFENDER, MIDFIELDER, FORWARD; } |
物件的封裝
下一步是關於安全性和封裝:目標是最小化可訪問性,因此只需將所有欄位定義為私有,那麼下一步是啥?使用public公開化 getter 和 setter 方法?方法的訪問方式預設應該是protected,這是基於封裝考慮的,考慮本文:
- 在系統示例中,球員不會更改電子郵件,姓名和職位。因此,它不需要setter方法。
- 最後一年last year的欄位表示玩家何時按合同離開球隊。當它是可選時,意味著沒有期望球員離開俱樂部。setter方法是必需的,但last year離職期必須等於或大於入職兩份。此外,在1863年足球出生之前的球員是無法玩足球比賽。
- 只有團隊可以處理它的球員; 它必須是緊耦合(高聚合)
在 Team 類中,有一個用於新增球員的方法;getter方法可以返回團隊中的所有球員。新增球員必須驗證,例如不能新增空球員或不能對於於20個球員。對getter返回集合的關鍵點是直接返回集合例項時,客戶端可能會使用該方法直接將新元素寫入集合,例如clean,add等,因此要解決封裝問題,一個好的做法是返回一個只讀例項,例如unmodifiableList:
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; public class Team { static final int SIZE = 20; private String name; private List<Player> players = new ArrayList<>(); @Deprecated Team() { } private Team(String name) { this.name = name; } public String getName() { return name; } public void add(Player player) { Objects.requireNonNull(player, "player is required"); if (players.size() == SIZE) { throw new IllegalArgumentException("The team is full"); } this.players.add(player); } public List<Player> getPlayers() { return Collections.unmodifiableList(players); } public static Team of(String name) { return new Team(Objects.requireNonNull(name, "name is required")); } } |
下一步是關於Player類設計,所有欄位都有一個getter 方法,end欄位除外:
import java.math.BigDecimal; import java.util.Objects; import java.util.Optional; public class Player { private String name; private Integer start; private Integer end; private String email; private Position position; private BigDecimal salary; private int goal = 0; public String getName() { return name; } public Integer getStart() { return start; } public String getEmail() { return email; } public Position getPosition() { return position; } public BigDecimal getSalary() { return salary; } public Optional<Integer> getEnd() { return Optional.ofNullable(end); } public void setEnd(Integer end) { if (end != null && end <= start) { throw new IllegalArgumentException("the last year of a player must be equal or higher than the start."); } this.end = end; } } public int getGoal() { return goal; } public void goal() { goal++; } |
getEnd()使用Optional返回一個可能為空的欄位,setEnd欄位用於更新該球員離職情況,當然離職日期不能大於入職日期。(banq注:使用Lombok時會忽略這個問題)
例項建立
前面討論了public和private以及protected的糾結使用,現在該討論例項建立了,首先我們可能會建立一個接收所有引數的建構函式,這適合Team類,因為它有一個name引數,但是在球員Player中會有幾個問題:
- 首先是引數數量; 由於幾個原因,多個建構函式並不是一個好習慣。例如,如果相同型別的引數太多,則在更改順序時可能會出錯。
- 第二個是關於這些驗證的複雜性。
兩個步驟解決:
第一步是型別定義。當一個物件具有諸如金錢,日期之類的巨大複雜性時,使用型別定義是有意義的。下面是郵件型別:
import java.util.Objects; import java.util.function.Supplier; import java.util.regex.Pattern; public final class Email implements Supplier<String> { private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@" + "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; private static final Pattern PATTERN = Pattern.compile(EMAIL_PATTERN); private final String value; @Override public String get() { return value; } private Email(String value) { this.value = value; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Email email = (Email) o; return Objects.equals(value, email.value); } @Override public int hashCode() { return Objects.hashCode(value); } @Override public String toString() { return value; } public static Email of(String value) { Objects.requireNonNull(value, "o valor é obrigatório"); if (!PATTERN.matcher(value).matches()) { throw new IllegalArgumentException("Email nao válido"); } return new Email(value); } } |
建立了電子郵件型別後,我們有了Player類的新版本:
import javax.money.MonetaryAmount; import java.time.Year; import java.util.Objects; import java.util.Optional; public class Player { private String id; private String name; private Year start; private Year end; private Email email; private Position position; private MonetaryAmount salary; //... } |
構建器模式
Builder模式遵循負責建立球員例項的責任,它避免了更改輸入引數順序可能導致的錯誤。
通常我們還是需要一個預設建構函式,將Deprecated 註釋放在此建構函式上以顯示它不是推薦的方法,內部類適合用於製造構建器,因為它可以建立僅訪問球員構建器的私有建構函式。
import javax.money.MonetaryAmount; import java.time.Year; import java.util.Objects; import java.util.Optional; public class Player { static final Year SOCCER_BORN = Year.of(1863); //hide private Player(String name, Year start, Year end, Email email, Position position, MonetaryAmount salary) { this.name = name; this.start = start; this.end = end; this.email = email; this.position = position; this.salary = salary; } @Deprecated Player() { } public static PlayerBuilder builder() { return new PlayerBuilder(); } public static class PlayerBuilder { private String name; private Year start; private Year end; private Email email; private Position position; private MonetaryAmount salary; private PlayerBuilder() { } public PlayerBuilder withName(String name) { this.name = Objects.requireNonNull(name, "name is required"); return this; } public PlayerBuilder withStart(Year start) { Objects.requireNonNull(start, "start is required"); if (Year.now().isBefore(start)) { throw new IllegalArgumentException("you cannot start in the future"); } if (SOCCER_BORN.isAfter(start)) { throw new IllegalArgumentException("Soccer was not born on this time"); } this.start = start; return this; } public PlayerBuilder withEnd(Year end) { Objects.requireNonNull(end, "end is required"); if (start != null && start.isAfter(end)) { throw new IllegalArgumentException("the last year of a player must be equal or higher than the start."); } if (SOCCER_BORN.isAfter(end)) { throw new IllegalArgumentException("Soccer was not born on this time"); } this.end = end; return this; } public PlayerBuilder withEmail(Email email) { this.email = Objects.requireNonNull(email, "email is required"); return this; } public PlayerBuilder withPosition(Position position) { this.position = Objects.requireNonNull(position, "position is required"); return this; } public PlayerBuilder withSalary(MonetaryAmount salary) { Objects.requireNonNull(salary, "salary is required"); if (salary.isNegativeOrZero()) { throw new IllegalArgumentException("A player needs to earn money to play; otherwise, it is illegal."); } this.salary = salary; return this; } public Player build() { Objects.requireNonNull(name, "name is required"); Objects.requireNonNull(start, "start is required"); Objects.requireNonNull(email, "email is required"); Objects.requireNonNull(position, "position is required"); Objects.requireNonNull(salary, "salary is required"); return new Player(name, start, end, email, position, salary); } } } |
根據此原則使用構建器模式,Java開發人員知道例項何時存在並具有有效資訊:
CurrencyUnit usd = Monetary.getCurrency(Locale.US); MonetaryAmount salary = Money.of(1 _000_000, usd); Email email = Email.of("marta@marta.com"); Year start = Year.now(); Player marta = Player.builder().withName("Marta") .withEmail(email) .withSalary(salary) .withStart(start) .withPosition(Position.FORWARD) .build(); |
Team類不需要了,因為它已經很平滑了:
Team bahia = Team.of("Bahia"); Player marta = Player.builder().withName("Marta") .withEmail(email) .withSalary(salary) .withStart(start) .withPosition(Position.FORWARD) .build(); bahia.add(marta); |
當Java開發人員談論驗證時,無法避開實現驗證的Java規範:Bean Validation。這使得Java開發人員可以更方便地使用註釋建立驗證。至關重要的是要指出BV不會使POO概念無效。換句話說,避免鬆散耦合,SOLID原則仍然有效,而不是放棄那些概念。
因此,BV可以仔細檢查驗證或執行驗證 Builder 以返回例項,只有它傳遞了驗證。
換句話說,SOLID原則仍然有效,因此,BV可以仔細檢查驗證或執行驗證 Builder以返回例項,只有它傳遞了驗證。
import javax.money.MonetaryAmount; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.PastOrPresent; import javax.validation.constraints.PositiveOrZero; import java.time.Year; import java.util.Objects; import java.util.Optional; public class Player { static final Year SOCCER_BORN = Year.of(1863); @NotBlank private String name; @NotNull @PastOrPresent private Year start; @PastOrPresent private Year end; @NotNull private Email email; @NotNull private Position position; @NotNull private MonetaryAmount salary; @PositiveOrZero private int goal = 0; //continue } import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; public class Team { static final int SIZE = 20; @NotBlank private String name; @NotNull @Size(max = SIZE) private List<Player> players = new ArrayList<>(); //continue } |
總而言之,本文演示瞭如何使用最佳設計實踐使程式碼防彈。此外,我們同時獲得物件和資料完整性。這些技術與儲存技術無關 - 開發人員可以在任何企業軟體中使用這些原則。重要的是說測試是必不可少的,但這超出了文章的範圍。
可以在GitHub上找到原始碼。
相關文章
- 使用Builder設計模式實現不變性 - DZone JavaUI設計模式Java
- 如何在不使用POJO程式碼的情況下建立Java批處理插入 -DZone JavaPOJOJava
- 從貧血模型到充血模型模型
- 如何輕鬆地為EA建立程式碼生成器 -DZone JavaJava
- 充血模型與貧血模型的再論模型
- 貧血模型與充血模型的對比模型
- 如何使用OO和函式式兩個方法實現重構? - DZone函式
- 如何使用 Kubernetes 實現應用程式的彈性伸縮
- 如何實現程式碼高亮
- 高防伺服器如何實現防禦伺服器
- 如何實現彈性架構架構
- canvas實現的彈力小球效果程式碼例項Canvas
- PHP 防抖(防重複提交)有哪些實現方法,具體程式碼PHP
- React如何原生實現防抖?React
- 使用簡單的Java程式碼實現酒店管理系統Java
- 10行Java程式碼實現最近被使用(LRU)快取Java快取
- java 橋接模式實現程式碼Java橋接模式
- 使用cookie讓彈窗只出現一次程式碼Cookie
- 如何用Java與python程式碼解釋IO模型JavaPython模型
- 貧血模型與充血模型比較 - DDD - The Domain Driven Design模型AI
- Laravel核心程式碼學習 — 模型關聯底層程式碼實現Laravel模型
- Laravel核心程式碼學習 -- 模型關聯底層程式碼實現Laravel模型
- 實踐篇 | DDD概念複雜難懂,實際落地如何設計程式碼實現模型?模型
- Java反射增強:程式碼模型Java反射模型
- Go使用websocket實現彈幕功能GoWeb
- 微信群活碼以及微信活碼防封如何實現技術分享
- 如何不使用js實現滑鼠hover彈出選單效果JS
- 隨機快速排序Java程式碼實現隨機排序Java
- 雜湊表的程式碼實現(Java)Java
- Java程式碼實現下載檔案Java
- js實現的點選彈出確認視窗程式碼JS
- 微信域名連結防封短連結是如何生成的,微信域名防封短連結程式碼實現示例
- 使用程式碼實現Autolayout的方法
- 如何使用大模型實現突破性創新研究?大模型
- 層次分析法模型原理以及程式碼實現模型
- Java大型資料集合實現並行加速處理幾種方法 - DZoneJava並行
- 教你使用1行程式碼在Java中實現字串的逆序行程Java字串
- java程式碼實現檢視Tomcat記憶體使用情況JavaTomcat記憶體