在域驅動設計中使用狀態模式

域驅動設計(DDD)是一種軟件開發方法,其中,通過將實現與核心業務概念的不斷發展的模型相連接,可以解決問題的復雜性。 該術語是由Eric Evans創造的,并且有一個DDD專用站點可以促進其使用。 根據其定義( “域驅動設計術語表” ),DDD是一種軟件開發方法,它建議:
  1. 對于大多數軟件項目,主要重點應該放在域和域邏輯上
  2. 復雜的領域設計應基于模型。

DDD促進了技術專家和領域專家之間的創造性合作,以迭代方式切入問題的概念核心。 請注意,沒有該領域專家的幫助,技術專家可能無法完全理解領域的復雜性,而領域專家在沒有技術專家幫助的情況下就無法實際應用其知識。

在許多情況下,領域模型對象封裝了內部狀態,本質上就是元素的歷史,即對象以有狀態方式運行。 在那種情況下,對象保持其私有狀態,這最終會影響其行為。 為了表示對象的狀態以及以干凈的方式處理其狀態轉換,可以使用狀態設計模式 。 簡而言之, 狀態模式可以解決如何使行為取決于狀態的問題。

顯然,DDD與狀態設計模式緊密相關。 我是DDD的新手,所以我將讓我們最好的JCG合作伙伴之一 Tomasz Nurkiewicz 通過使用State Design Pattern的示例向您介紹DDD 。

(注意:對原始帖子進行了少量編輯以提高可讀性)

許多企業應用程序中的某些領域對象都包含狀態的概念。 國家有兩個主要特征:

  • 域對象的行為(其對業務方法的響應方式)取決于其狀態
  • 業務方法可能會更改對象的狀態,從而迫使對象在調用特定方法后的行為有所不同。

如果您無法想象域對象狀態的任何真實示例,請考慮租賃公司中的Car實體。 小汽車在保留相同對象的同時,還有一個附加標志,稱為狀態,這對于公司至關重要。 狀態標志可以具有三個值:

  1. 可用的
  2. 出租
  3. 失蹤

顯然,目前無法租用處于RENTED或MISSING狀態的Car,并且rent()方法應該失敗。 但是,當汽車退回并且其狀態為AVAILABLE時,除了記住已租車的客戶外,在Car實例上調用rent()應該應該將汽車狀態更改為RENTED。 狀態標志(可能是數據庫中的單個字符或整數)是對象狀態的一個示例,因為它影響業務方法,反之亦然,業務方法可以更改它。

現在想一會兒,您將如何實現這種方案,我相信您已經在工作中見過很多次了。 您有許多業務方法,具體取決于當前狀態,也可能取決于多個狀態。 如果您喜歡面向對象的編程,則可能會立即考慮繼承并創建擴展Car的AvailableCar,RentedCar和MissingCar類。 它看起來不錯,但是非常不切實際,特別是當Car是一個持久對象時。 實際上,這種方法設計得不好:改變的不是整個對象,而是內部狀態的一部分–我們不是在替換對象,而只是在更改它。 也許您考慮過在每個方法中根據狀態執行不同任務的if-else-if-else級聯。 相信我,不要去那里,那是通往代碼維護地獄的道路。

取而代之的是,我們將使用繼承和多態性,但是要采用一種更為巧妙的方式:使用State GoF模式 。 例如,我選擇了一個名為Reservation的實體,該實體可以具有以下狀態之一:

生命周期流程很簡單:創建保留時,它具有NEW狀態(狀態)。 然后,一些授權人員可以接受預訂,例如導致臨時預訂座位,并向用戶發送一封電子郵件,要求他為預訂付款。 然后,當用戶執行匯款時,將進行入帳,打印票證并將第二封電子郵件發送給客戶。

當然,您知道某些動作的副作用取決于保留當前狀態。 例如,您可以隨時取消預訂,但是根據預訂狀態,這可能會導致退款和取消預訂,或者僅向用戶發送電子郵件。 此外,某些操作在特定狀態下(用戶將錢轉至已取消的預訂該怎么辦)毫無意義,或應被忽略。 現在想象一下,如果必須為每個狀態和每個方法使用if-else構造,那么編寫上面狀態機圖上公開的每個業務方法將有多么困難。

為了解決此問題,我將不解釋原始的GoF State設計模式。 相反,我將使用Java枚舉功能介紹這種模式的一些變化。 代替為狀態抽象創建抽象類/接口并為每個狀態編寫實現,我僅創建了一個包含所有可用狀態/狀態的枚舉:

public enum ReservationStatus {NEW,ACCEPTED,PAID,CANCELLED;
}

我還根據該狀態為所有業務方法創建了一個接口。 將此接口視為所有狀態的抽象基礎,但是我們將以稍微不同的方式使用它:

public interface ReservationStatusOperations {ReservationStatus accept(Reservation reservation);ReservationStatus charge(Reservation reservation);ReservationStatus cancel(Reservation reservation);
}

最后是Reservation域對象,它恰好同時是一個JPA實體(省略了getters / setter,或者也許我們可以只使用Groovy而忘記它們了?):

public class Reservation {private int id;private String name;private Calendar date;private BigDecimal price;private ReservationStatus status = ReservationStatus.NEW;//getters/setters}

如果Reservation是一個持久域對象,則其狀態(ReservationStatus)顯然也應該是持久的。 這種觀察將我們帶到了使用枚舉而不是抽象類的第一個重大優勢:JPA / Hibernate可以使用枚舉的名稱或序數值(默認情況下)輕松地序列化Java枚舉并將其保留在數據庫中。 在原始GoF模式中,我們寧愿將ReservationStatusOperations直接放在域對象中,并在狀態更改時切換實現。 我建議使用枚舉,僅更改枚舉值。 使用枚舉的另一個優點(以框架為中心,更不重要)是將所有可能的狀態都列在一個位置。 您無需搜尋源代碼即可搜索基狀態類的所有實現,所有內容都可以在一個逗號分隔的列表中看到。

好吧,深吸一口氣,現在我將解釋所有這些部分如何協同工作以及到底為什么ReservationStatusOperations中的業務操作返回ReservationStatus。 首先,您必須回顧實際的枚舉是什么。 它們不僅僅是像C / C ++中的單個名稱空間中的常量的集合。 在Java中,枚舉是一組封閉的類集,它們從一個通用的基類(例如ReservationStatus)繼承,而該基類又從Enum繼承。 因此,在使用枚舉時,我們可能會利用多態和繼承:

public enum ReservationStatus implements ReservationStatusOperations {NEW {public ReservationStatus accept(Reservation reservation) {//..}public ReservationStatus charge(Reservation reservation) {//..}public ReservationStatus cancel(Reservation reservation) {//..}
},ACCEPTED {public ReservationStatus accept(Reservation reservation) {//..}public ReservationStatus charge(Reservation reservation) {//..}public ReservationStatus cancel(Reservation reservation) {//..}
},PAID {/*...*/},CANCELLED {/*...*/};}

盡管試圖以這種方式編寫ReservationStatusOperations很誘人,但對于長期開發而言,這是一個壞主意。 不僅枚舉源代碼會很長(已實現方法的總數等于狀態數量乘以業務方法的數量),而且設計不好(單個類中所有狀態的業務邏輯)。 同樣,對于在過去兩周內未通過SCJP考試的任何人來說,實現與該語法的其余部分一起使用的接口的枚舉可能都是相反的。 相反,我們將提供一個簡單的間接級別,因為“ 計算機科學中的任何問題都可以通過另一層間接解決 ”。

public enum ReservationStatus implements ReservationStatusOperations {NEW(new NewRso()),ACCEPTED(new AcceptedRso()),PAID(new PaidRso()),CANCELLED(new CancelledRso());private final ReservationStatusOperations operations;ReservationStatus(ReservationStatusOperations operations) {this.operations = operations;}@Overridepublic ReservationStatus accept(Reservation reservation) {return operations.accept(reservation);}@Overridepublic ReservationStatus charge(Reservation reservation) {return operations.charge(reservation);}@Overridepublic ReservationStatus cancel(Reservation reservation) {return operations.cancel(reservation);}}

這是我們ReservationStatus枚舉的最終源代碼(無需實現ReservationStatusOperations)。 簡而言之:每個枚舉值都有其自己的ReservationStatusOperations(簡稱Rso)的不同實現。 此實現作為構造函數參數傳遞,并分配給名為operation的最終字段。 現在,每當在枚舉上調用業務方法時,它將被委派給該枚舉專用的ReservationStatusOperations實現:

ReservationStatus.NEW.accept(reservation);       // will call NewRso.accept()
ReservationStatus.ACCEPTED.accept(reservation);  // will call AcceptedRso.accept()

最后一個難題是Reservation域對象,包括業務方法:

public void accept() {setStatus(status.accept(this));
}public void charge() {setStatus(status.charge(this));
}public void cancel() {setStatus(status.cancel(this));
}public void setStatus(ReservationStatus status) {if (status != null && status != this.status) {log.debug("Reservation#" + id + ": changing status from " +this.status + " to " + status);this.status = status;}

這里會發生什么? 在保留域對象實例上調用任何業務方法時,將在ReservationStatus枚舉值上調用相應的方法。 根據當前狀態,將調用不同的方法(具有不同的ReservationStatusOperations實現)。 但是沒有切換用例或if-else構造,只有純多態性。 例如,如果您在狀態字段指向ReservationStatus.ACCEPTED,AcceptedRso.charge()的情況下調用charge(),則向預訂的客戶收取費用,并且預訂狀態更改為PAID。

但是,如果我們在同一實例上再次調用charge()會發生什么呢? status字段現在指向ReservationStatus.PAID,因此將執行PaidRso.charge(),這將引發業務異常(對已付費的預訂收取費用無效)。 在沒有條件代碼的情況下,我們使用對象本身包含的業務方法實現了狀態感知域對象。

我還沒有提到的一件事是如何從業務方法更改狀態。 這是與原始GoF模式的第二個區別。 我沒有將StateContext實例傳遞給每個可用于更改狀態的狀態感知操作(例如accept()或charge()),而是僅從業務方法返回新狀態。 如果狀態不為null,并且與前一個狀態不同(setStatus()方法),則保留將轉換為給定狀態。 讓我們看一下它如何在AcceptedRso對象上工作(當Reservation處于ReservationStatus.ACCEPTED狀態時,將執行其方法):

public class AcceptedRso implements ReservationStatusOperations {@Overridepublic ReservationStatus accept(Reservation reservation) {throw new UnsupportedStatusTransitionException("accept", ReservationStatus.ACCEPTED);}@Overridepublic ReservationStatus charge(Reservation reservation) {//charge client's credit card//send e-mail//print ticketreturn ReservationStatus.PAID;}@Overridepublic ReservationStatus cancel(Reservation reservation) {//send cancellation e-mailreturn ReservationStatus.CANCELLED;}}

僅需閱讀上面的課程,即可很容易地了解處于“已接受”狀態的預訂行為:第二次嘗試接受(已接受預訂時)將引發異常,收費將向客戶的信用卡收取費用,向其打印一張機票并發送電子郵件等。此外,收費會返回PAID狀態,這將導致預訂轉移到該狀態。 這意味著另一個對charge()的調用將由不同的ReservationStatusOperations實現(PaidRso)處理,沒有條件代碼。

這將全部與國家模式有關。 如果您對這種設計模式不滿意,請與使用條件代碼的經典方法進行比較,并比較工作量和出錯率。 還要考慮一會兒,添加新的狀態或與狀態有關的操作時需要什么,以及閱讀這樣的代碼有多容易。

我沒有顯示所有ReservationStatusOperations實現,但是如果您想在基于Spring或EJB的Java EE應用程序中引入這種方法,那么您可能會發現其中的一個大謊言。 我評論了每種業務方法中應發生的情況,但未提供實際的實現。 我沒有,因為我遇到了一個大問題:一個Reservation實例是手工創建的(使用新的)或由諸如Hibernate之類的持久性框架創建的。 它使用靜態創建的枚舉,該枚舉可手動創建ReservationStatusOperations實現。 無法將任何依賴項,DAO和服務注入此類,因為它們的生命周期是在Spring或EJB容器范圍之外進行控制的。 實際上,有一個使用Spring和AspectJ的簡單而強大的解決方案。 但是請耐心等待,我將在下一篇文章中詳細解釋它,為我們的應用程序添加一些域驅動的風格。

而已。 我們的JCG合作伙伴 Tomasz Nurkiewicz撰寫了一篇非常有趣的文章,介紹如何在DDD方法中利用狀態模式 。 我當然很期待本教程的下一部分,該教程將在幾天后在JavaCodeGeeks上托管。 更新:下一部分是使用Spring和AspectJ的域驅動設計 。

相關文章 :
  • Spring和AspectJ的領域驅動設計
  • 零XML的Spring配置
  • 正確記錄應用程序的10個技巧
  • 每個程序員都應該知道的事情
  • 依賴注入–手動方式

翻譯自: https://www.javacodegeeks.com/2011/02/state-pattern-domain-driven-design.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/374761.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/374761.shtml
英文地址,請注明出處:http://en.pswp.cn/news/374761.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用selenium進行密碼破解(繞過賬號密碼JS加密)

經常碰到網站,賬號密碼通過js加密后進行提交。通過burp攔截抓到的賬號密碼是加密后的,所以無法通過burp instruder進行破解。只能模擬瀏覽器填寫表單并點擊登錄按鈕進行破解。于是想到了自動化web測試工具selenium,代碼如下,測試效…

力扣刪除排序數組中的重復項

給你一個有序數組 nums ,請你 原地 刪除重復出現的元素,使每個元素 只出現一次 ,返回刪除后數組的新長度。 不要使用額外的數組空間,你必須在 原地 修改輸入數組 并在使用 O(1) 額外空間的條件下完成。 我沒注意到“有序”這一條…

POJ1789-Truck History .

題目鏈接:http://poj.org/problem?id1789 題目的大概意思就是給你n個字符串。每個字符串只有7的長度。然后分別給這些字符串編號。不同編號之間的距離就是他們有多少個不同的字母。(同一個位置字母不相同也算)然后一個編號只能由另一個派生…

Java Fork / Join進行并行編程

最近幾年,計算機處理器領域發生了范式轉變。 多年來,處理器制造商一直在提高時鐘頻率,因此開發人員享受到這樣的事實,即他們的單線程軟件執行得更快,而無需他們付出任何努力。 現在,處理器制造商青睞多核芯…

arm-elf-gcc交叉編譯器的使用教程

arm-elf-gcc交叉編譯器的使用教程 一開始需要安裝arm-elf-gcc,但是這是一個32位的程序,我是安裝了64位的系統,據說安裝ia32.libs依賴庫能運行這個,但是看到博客上面前人安裝完了系統圖標少了一半,然后就怕了。經過了翻…

力扣刪除排序數組中的重復項 II

給你一個有序數組 nums ,請你 原地 刪除重復出現的元素,使每個元素 最多出現兩次 ,返回刪除后數組的新長度。 不要使用額外的數組空間,你必須在 原地 修改輸入數組 并在使用 O(1) 額外空間的條件下完成。 思路: 雙指針…

2 android學習資料

http://blog.csdn.net/lmj623565791 http://blog.csdn.net/harvic880925/article/details/50995268轉載于:https://www.cnblogs.com/YyuTtian/p/5440930.html

建立自己的GWT Spring Maven原型

大家好, 在觀看Justin撰寫的有關Spring和GWT的非常有趣的文章時,我認為展示如何構建自己的自定義Maven原型非常有用。我們將展示的原型基于Justin的上一個項目,并包括各種技術,例如Spring , GWT , AspectJ…

C# 連接Oracle數據庫異常總結

這2天因為工作需要連接Oracle數據庫,中間發生了很多問題 一、使用OleDbConnection連接數據庫 ------------------ ProviderOraOLEDB.Oracle.1;User IDsajet;Passwordtech;Data Source(DESCRIPTION (ADDRESS_LIST (ADDRESS (PROTOCOL TCP)(HOST 192.168.66.225)(…

力扣顏色分類

給定一個包含紅色、白色和藍色,一共 n 個元素的數組,原地對它們進行排序,使得相同顏色的元素相鄰,并按照紅色、白色、藍色順序排列。 此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。 思路:將紅色和藍色…

Cassandra,MongoDB,CouchDB,Redis,Riak,HBase比較

克里斯托夫科瓦奇(KristfKovcs)對六個最受歡迎的“ NoSQL ”數據庫實現進行了非常有趣的簡短比較 。 除了Kristf的工作之外,我還想提供一些鏈接,我相信這些鏈接將對有興趣關注“ NoSQL ”社區的所有人員提供幫助: No…

程序員需要謹記的九大安全編碼規則

歷史已經證明,軟件設計的缺陷一直是導致其漏洞被利用的最主要的罪魁禍首。安全專家發現,多數漏洞源自常見軟件中相對有限的一些漏洞。軟件開發者和設計者應當嚴格檢查程序中的各種錯誤,盡量在軟件部署之前就減少或清除其中的漏洞。 下面列舉的…

HDU 2897

Problem Description當日遇到月,于是有了明。當我遇到了你,便成了侶。那天,日月相會,我見到了你。而且,大地失去了光輝,你我是否成侶?這注定是個凄美的故事。(以上是廢話&#xff09…

力扣合并兩個有序數組

題目:給你兩個按 非遞減順序 排列的整數數組 nums1 和 nums2,另有兩個整數 m 和 n ,分別表示 nums1 和 nums2 中的元素數目。 請你 合并 nums2 到 nums1 中,使合并后的數組同樣按 非遞減順序 排列。 注意:最終&#…

Google Guava庫必需品

我希望代碼簡單,短而又易于閱讀。 不必要的復雜性分散了人們對真實情況的理解,使他們難以理解,并且可能成為生產力的真正殺手。 您知道,纏結的for循環和索引可以跟蹤是否/其他情況和切換用例,空/驗證檢查,轉…

單調棧3_水到極致的題 HDOJ4252

A Famous City 題目大意 給出正視圖 每一列為樓的高度 最少有幾座樓 坑點 樓高度可以為0 代表沒有樓 貢獻了兩發RE 原因 if(!s.empty()&&tem){s.push(tem); continue;}并不能篩去 空棧且 tem為0的情況 改為 if(!s.empty()){if(tem) s.push(tem); continue;} 后AC 題目…

eclipse配置遠程調試

一、配置 1、cd apache-tomcat/bin 2、vi startup.sh文件 3、在文件開頭處,添加下方代碼(address代表的是調試端口) declare -x CATALINA_OPTS"-Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:servery,transportdt_socket,suspendn,address…

力扣兩數之和 II - 輸入有序數組

題目:給定一個已按照 非遞減順序排列 的整數數組 numbers ,請你從數組中找出兩個數滿足相加之和等于目標數 target 。 我的代碼: 對撞指針 class Solution {public int[] twoSum(int[] numbers, int target) {int low 0;//指向頭int high numbers.le…

SpringMVC 3 Tiles 2.2.2集成教程

Apache Tiles是基于Java的Web應用程序的流行且最常用的模板框架。 由于Struts 1.x使用Tiles作為其默認模板框架,因此Tiles變得更加流行。 SpringMVC是一個MVC框架,例如Struts ,也支持將Tiles集成為其模板框架。 讓我們看看如何集成SpringMVC和…

[團隊項目3.0]Scrum團隊成立

Scrum團隊成立 5.Scrum團隊成立 5.1 團隊名稱,團隊目標、團隊口號、團隊照; 5.2 角色分配 產品負責人: 決定開發內容和優先級排序,最大化產品以及開發團隊工作的價值。 Scrum Master: 負責確保團隊遵循 Scrum 的理論、實踐和規則。…