帶有Guice的富域模型

貧血域模型是一個非常常見的反模式。 在ORM和DI框架的世界中,我們自然會發現自己擁有一個由ORM管理的“域”,該域包含所有數據且無行為。 通過我們的DI框架有幫助地注入了輔助類,這些輔助類都是行為且沒有數據。

在本文中,我將介紹一種實現富域模型的可能方法-此示例使用Guice ,盡管我確信Spring等將具有實現同一目標的不同方法。

問題

所有源代碼都可以在github上找到。 “ master”分支顯示原始的,分解不良的代碼。 “富域”分支顯示了我描述的解決方案。

貧血領域模型

首先,我們的貧血領域模型– TradeOrder.java 。 像Hibernate一樣,該類具有大量的注釋,這些注釋描述了數據模型,所有數據的字段,訪問數據的訪問器和變異器,并且沒有其他有趣的東西。 我假設在此領域中,TradeOrders可以完成任務。 也許我們下訂單或取消訂單。 沿線某個地方,我們域中的關鍵對象可能應該具有某些行為。

@Entity
@Table(name="TRADE_ORDER")
public class TradeOrder {@Id@Column(name="ID", length=32)@GeneratedValueprivate String id;@ManyToOne@JoinColumn(name="CURRENCY_ID", nullable=false)@ForeignKey(name="FK_ORDER_CURRENCY")@AccessType("field")private Currency currency;@Column(name="AMOUNT", nullable=true)private BigDecimal amount;public TradeOrder() { }public String getId() { return id; }public Currency getCurrency() { return currency; }public void setCurrency(Currency currency) { this.currency = currency; }public BigDecimal getAmount() { return amount; }public void setAmount(BigDecimal amount) { this.amount = amount; }
}

助手類

在這個非常簡單的示例中,我們需要計算訂單的價值,即我們要購買(或出售)的股票數量和所支付的每股價格。 不幸的是,因為這涉及到依賴關系,所以該行為并不駐留在與其相關的類中,而是被轉移到了一個非常有用的幫助程序類中。

看一下FiguresFactory.java 。 該類只有一個公共方法– buildFrom。 此方法的目標是從TradeOrder創建圖形。

public Figures buildFrom(TradeOrder order, Date effectiveDate)throws OrderProcessingException {Date tradeDate = order.getTradeDate();HedgeFundAsset asset = order.getAsset();BigDecimal bestPrice = bestPriceFor(asset, tradeDate);return order.getType() == TradeOrderType.REDEMPTION? figuresFromPosition(order,lookupPosition(asset, order.getFohf(), tradeDate),lookupPosition(asset, order.getFohf(), effectiveDate),bestPrice): getFigures(order, bestPrice, null);
}

除了“生效日期”(無論可能是什么)之外,此方法僅需要輸入TradeOrder。 使用TradeOrder上大量的吸氣劑,它會要求操作數據,而不是告訴 TradeOrder需要什么。 在理想的,面向對象的系統中,這應該是TradeOrder上稱為createFigures的方法。

我們為什么到這里來? 都是依賴注入框架的錯! 因為創建Figures對象的過程需要我們解析價格和貨幣,所以我們需要使用可注入的依賴關系去查找這些數據。 我們的貧血領域無法注入依賴項,因此我們將其注入到這個小助手類中。

我們最終得到的是經典的貧血域模型。 TradeOrder具有數據; 而許多輔助類(例如FiguresFactory)包含對數據進行操作的行為。 都是非OO的。

更好的方法

資料記錄

第一步是創建一個簡單的值對象以映射數據庫中的行–我將其稱為TradeOrderRecord.java 。 這看起來很像原始域對象,只是我刪除了訪問器和增變器以清楚地表明這是一個沒有行為的值對象。

為了使構造這些記錄對象更容易,我使用了karg,這是我的一位同事編寫的一個庫–這要求我們聲明可用于構造記錄的一組參數,以及一個接受參數列表的構造函數。 這極大地簡化了構造,并且避免了我們擁有需要27個字符串的構造函數(例如)。

@Entity
@Table(name="TRADE_ORDER")
public class TradeOrderRecord {@Id@Column(name="ID", length=32)@GeneratedValuepublic String id;@Column(name="CURRENCY_ID")public String currencyId;@Column(name="AMOUNT", nullable=true)public BigDecimal amount;public static class Arguments {public static final Keyword<String> CURRENCY_ID = newKeyword();public static final Keyword<BigDecimal> AMOUNT = newKeyword();}protected TradeOrderRecord() { }public TradeOrderRecord(KeywordArguments arguments) {this.currencyId = Arguments.CURRENCY_ID.from(arguments);this.amount = Arguments.AMOUNT.from(arguments);}
}

富域

我們的目標是使TradeOrder成為一個豐富的域對象-它應該具有與“ TradeOrder”的域概念相關的所有行為和數據。

數據

TradeOrder首先需要在內部存儲與TradeOrder相關的所有數據(至少作為起點,未使用的字段暗示我們可能能夠進一步簡化此操作)。

public class TradeOrder {private final String id;private final String currencyId;private final BigDecimal amount;

我們使數據不可變 。 不可變狀態通常是一件好事–在這里,它迫使我們清楚這是一個完全填充的TradeOrder,并且由于它具有ID,因此它始終與數據庫中的一行關聯。 通過使TradeOrder不可變,顯而易見的問題是–如何更新訂單? 嗯,我們可以選擇多種方式來做到這一點–但這是在不同時間的不同故事。

我們也不需要訪問器。 由于我們計劃將與TradeOrder相關的所有行為放在TradeOrder類本身上,因此其他類不需要詢問信息,它們只需要告訴我們他們想要實現什么。

注意:有一個(現在不建議使用)訪問器–暗示應進一步移動的行為。

依存關系

除了用于存儲數據的字段之外,TradeOrder還將具有表示可注入依賴項的字段。

private final CurrencyCache currencyCache;
private final PriceFetcher bestPriceFetcher;
private final PositionFetcher hedgeFundAssetPositionsFetcher;
private final FXService fxService;

有些人會發現這種冒犯性-將依賴項與數據混合在一起。 但是,就我個人而言,我認為這種權衡是值得的–能夠在與之相關的類上定義我們的行為的好處是值得的。

行為

現在我們將數據和依賴項全部集中在一處,在FiguresFactory的方法之間移動相對容易:

public Figures createFigures(Date effectiveDate) throws OrderProcessingException {BigDecimal bestPrice = bestPriceFor(this.asset, this.tradeDate);return this.type == TradeOrderType.REDEMPTION? figuresFromPosition(fohf,lookupPosition(this.asset, fohf, this.tradeDate),lookupPosition(this.asset, fohf, effectiveDate), bestPrice): getFigures(fohf, bestPrice, null);
}

施工

我們需要解決的最后一件事是如何創建TradeOrder實例。 由于數據和依賴項的字段都標記為final,因此構造函數必須將它們全部初始化。 這意味著我們需要一個帶有依賴關系的構造函數和一個TradeOrderRecord(即我們從數據庫中讀取的值對象):

@Inject
protected TradeOrder(CurrencyCache currencyCache,PriceFetcher bestPriceFetcher,PositionFetcher hedgeFundAssetPositionsFetcher,FXService fxService,@Assisted TradeOrderRecord record) {...
}

這不是特別漂亮,但是要注意的關鍵是@Assisted注釋。 這使我們可以告訴Guice,其他依賴項已正常注入,而TradeOrderRecord應該從工廠方法傳遞過來。 工廠界面本身如下所示:

public static interface Factory {TradeOrder create(TradeOrderRecord record);
}

我們不需要實現此接口,Guice會自動提供它。 當需要創建TradeOrder實例時,可以在其他地方使用TradeOrder.Factory成為可注入依賴項。 Guice會像平常一樣初始化可注入依賴關系,輔助依賴關系– TradeOrderRecord –從工廠傳遞過來。 因此,我們的調用代碼無需擔心我們的富域需要可注入的依賴項。

@Inject private TradeOrder.Factory tradeOrderFactory;
...
TradeOrderRecord record = tradeOrderDAO.loadById(id);
TradeOrder order = tradeOrderFactory.create(record);

結論

通過將依賴項和數據混合到一個豐富的域模型中,我們可以定義具有正確行為的類。 現在,TradeOrder中明顯的代碼味道是創建圖形的詳細機制可能是一個單獨的問題,應予以分解。 沒關系,我們可以引入一個新的依賴項來進行管理-只要TradeOrder仍然是構造Figures對象的起點。

通過將行為放在一個地方,我們的域模型更易于使用,更易于推理,也更容易發現重復或相似的情況。 然后,我們可以使用良好的對象模型進行合理的重構,而不是將任意的區別引入到作為函數庫而不是域參與者的幫助器類中。

參考: 實施富域M 來自JCG合作伙伴 David Green的Guice先生在Actively Lazy Blog上發表的評論 。

相關文章 :
  • 在領域驅動的設計,貧乏的領域模型,代碼生成,依賴項注入等方面……
  • 在域驅動設計中使用狀態模式
  • Spring和AspectJ的領域驅動設計
  • Spring依賴注入技術的發展
  • 什么是依賴倒置? 是IoC嗎?

翻譯自: https://www.javacodegeeks.com/2011/10/rich-domain-model-with-guice.html

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

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

相關文章

php匿名函數小示例

<?php //$fun function($params){ // echo $params; //}; // //$fun(aa);//例一 //在普通函數中定義一個匿名函數 //function printStr(){ // $fun function($something){ // echo $something; // }; // $fun(something); // //} //printStr();//例子…

購書心得

作者&#xff1a;泉哥主頁&#xff1a;http://riusksk.blogbus.com富家不用買良田&#xff0c;書中自有千鐘粟&#xff1b;安居不用架高堂&#xff0c;書中自有黃金屋&#xff1b;出門莫恨無人隨&#xff0c;書中車馬多如簇&#xff1b;娶妻莫恨無良媒&#xff0c;書中自有顏如…

MariaDB?條件語句WHERE

MariaDB 條件語句WHEREWHERE Clause Operators Operator Description Equality<> Nonequality! Nonequality< Less than< Less than or equal to > Greater than > Greater than or equal to BETWEEN Between two specified values BETWEEN AND (jlive)[c…

Spring 3.1緩存抽象教程

即將發布的Spring 3.1版本中引入的新功能之一是緩存抽象之一 。 Spring Framework提供了對將緩存透明添加到現有Spring應用程序中的支持。 與事務支持類似&#xff0c;緩存抽象允許一致使用各種緩存解決方案&#xff0c;而對代碼的影響最小。 從本質上講&#xff0c;抽象將緩存…

《Linux內核分析》 第四節 扒開系統調用的三層皮(上)

黃胤凱 原創作品轉載請注明出處 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000 一、視頻學習 1.系統調用的三層皮&#xff1a;xyz system_call sys_xyz 對應的是API&#xff0c;中斷向量對應的中斷服務程序&#xff0c;系統調用服務程…

如何在Java中獲得類似于C的性能

總覽 Java有許多可能很慢的領域。 但是&#xff0c;對于每個問題都有解決方案。 許多解決方案/黑客都需要解決Java的保護問題&#xff0c;但是如果您需要低水平的性能&#xff0c;還是可以的。 Java使高級編程變得更簡單容易&#xff0c;但代價是使低級編程變得更加困難。 幸…

STARTUPINFO結構

1.結構原型 typedef struct _STARTUPINFO { DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD w…

Spring聲明式事務示例

事務是具有ACID &#xff08;原子的&#xff0c;一致的&#xff0c;隔離的和持久的&#xff09;屬性的工作單元。 原子意味著所有更改都發生或什么都沒有發生。 如果從一個帳戶借錢并貸記到另一個帳戶&#xff0c;則交易將確保借記和貸項均已完成或均未完成。 一致表示更改使數…

路徑 (Path)–nodejs

本模塊包含一套用于處理和轉換文件路徑的工具集。幾乎所有的方法只做字符串變換&#xff0c; 不會調用文件系統檢查路徑是否有效。 通過 require(path) 來加載此模塊。以下是本模塊所提供的方法&#xff1a; path.normalize(p) 規范化字符串路徑&#xff0c;注意 .. 和 . 部分 …

OllyDBG反匯編快速找到程序入口一點分析

出處&#xff1a;http://hi.baidu.com/0soul/blog/item/b62f8f08c2c3c42c6b60fbbe.html 先聲明下&#xff1a;這個和脫殼沒關系&#xff0c;不是找殼里面的程序入口哦&#xff0c;只是程序本身的入口&#xff0c;個別朋友不要誤會哈。其實這個應該是基礎&#xff0c;但我經常找…

簡單的Twitter:Heroku上的Play框架,AJAX,CRUD

因此&#xff0c;重大的公告發布了– Heroku開始為Play Framework應用程序提供本機支持&#xff01; 如果您還沒有聽說過&#xff0c;請在Heroku的博客上查看Jesper Joergensen的帖子 。 因此&#xff0c;對于演示&#xff0c;我將建立一個非常基本的Twitter副本&#xff1b; 它…

Cron表達式

CronTrigger CronTriggers往往比SimpleTrigger更有用&#xff0c;如果您需要基于日歷的概念&#xff0c;而非SimpleTrigger完全指定的時間間隔&#xff0c;復發的發射工作的時間表。CronTrigger&#xff0c;你可以指定觸發的時間表如“每星期五中午”&#xff0c;或“每個工作日…

深入理解JavaScript學習筆記(3)_全面解析Module模式

簡介 Module模式是JavaScript編程中一個非常通用的模式&#xff0c;一般情況下&#xff0c;大家都知道基本用法&#xff0c;本文嘗試著給大家更多該模式的高級使用方式。 首先我們來看看Module模式的基本特征&#xff1a; 模塊化&#xff0c;可重用封裝了變量和function&#x…

匯編----乘指令: MUL、IMUL

MUL: 無符號乘 ;影響 OF、CF 標志位;指令格式:;MUL r/m ;參數是乘數;如果參數是 r8/m8, 將把 AL 做乘數, 結果放在 AX;如果參數是 r16/m16, 將把 AX 做乘數, 結果放在 EAX;如果參數是 r32/m32, 將把 EAX 做乘數, 結果放在 EDX:EAX IMUL: 有符號乘 ;影響 OF、CF 標志位;…

Google App Engine Java功能和命名空間API

功能API 使用Capabilities API&#xff0c;您的應用程序可以檢測特定API功能的停機和計劃停機時間。 您可以使用此API來檢測應用程序何時不可用&#xff0c;然后繞過它來減少應用程序的停機時間。 我們該如何處理&#xff0c;這是個折衷方案&#xff1f; 1.優雅&#xff1a;創…

破解key file時經常用到的幾個API函數及其用法

CreateFile函數 ================================================================================== CreateFile: Creates or opens a file or I/O device. The most commonly used I/O devices are as follows: file, file stream, directory, physical disk, volume, …

PHP計劃任務之關閉瀏覽器后仍然繼續執行的函數

函數名稱&#xff1a;ignore_user_abort 本函數配置或取得使用端連接中斷后&#xff0c;PHP 程序是否仍繼續執行。默認值為中斷連接后就停止執行。在 PHP 配置文件中 (php3.ini/php.ini) 的 ignore_user_abort 選項就是配置處。本功能在 PHP 3.0.7 版之后才開始提供。 官方說明…

node--更新數據庫問題

昨天測試blog的comment功能&#xff0c;在新增comment相關的代碼之后&#xff0c;重啟應用&#xff0c;出現Cannot call method forEach of undefined 。反復核對代碼&#xff0c;都沒發現異常&#xff0c;最后將數據庫文件刪除之后&#xff0c;再重啟數據庫&#xff0c;一切正…