带有Guice的富域模型
在本文中,我將介紹一種實(shí)現(xiàn)富域模型的可能方法-此示例使用Guice ,盡管我確信Spring等將具有實(shí)現(xiàn)同一目標(biāo)的不同方法。
問(wèn)題
所有源代碼都可以在github上找到。 “ master”分支顯示原始的,分解不良的代碼。 “富域”分支顯示了我描述的解決方案。
貧血領(lǐng)域模型
首先,我們的貧血領(lǐng)域模型– TradeOrder.java 。 像Hibernate一樣,該類(lèi)具有大量的注釋,這些注釋描述了數(shù)據(jù)模型,所有數(shù)據(jù)的字段,訪問(wèn)數(shù)據(jù)的訪問(wèn)器和變異器,并且沒(méi)有其他有趣的東西。 我假設(shè)在此領(lǐng)域中,TradeOrders可以完成任務(wù)。 也許我們下訂單或取消訂單。 沿線某個(gè)地方,我們域中的關(guān)鍵對(duì)象可能應(yīng)該具有某些行為。
@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; } }助手類(lèi)
在這個(gè)非常簡(jiǎn)單的示例中,我們需要計(jì)算訂單的價(jià)值,即我們要購(gòu)買(mǎi)(或出售)的股票數(shù)量和所支付的每股價(jià)格。 不幸的是,因?yàn)檫@涉及到依賴(lài)關(guān)系,所以該行為并不駐留在與其相關(guān)的類(lèi)中,而是被轉(zhuǎn)移到了一個(gè)非常有用的幫助程序類(lèi)中。
看一下FiguresFactory.java 。 該類(lèi)只有一個(gè)公共方法– buildFrom。 此方法的目標(biāo)是從TradeOrder創(chuàng)建圖形。
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); }除了“生效日期”(無(wú)論可能是什么)之外,此方法僅需要輸入TradeOrder。 使用TradeOrder上大量的吸氣劑,它會(huì)要求操作數(shù)據(jù),而不是告訴 TradeOrder需要什么。 在理想的,面向?qū)ο蟮南到y(tǒng)中,這應(yīng)該是TradeOrder上稱(chēng)為createFigures的方法。
我們?yōu)槭裁吹竭@里來(lái)? 都是依賴(lài)注入框架的錯(cuò)! 因?yàn)閯?chuàng)建Figures對(duì)象的過(guò)程需要我們解析價(jià)格和貨幣,所以我們需要使用可注入的依賴(lài)關(guān)系去查找這些數(shù)據(jù)。 我們的貧血領(lǐng)域無(wú)法注入依賴(lài)項(xiàng),因此我們將其注入到這個(gè)小助手類(lèi)中。
我們最終得到的是經(jīng)典的貧血域模型。 TradeOrder具有數(shù)據(jù); 而許多輔助類(lèi)(例如FiguresFactory)包含對(duì)數(shù)據(jù)進(jìn)行操作的行為。 都是非OO的。
更好的方法
資料記錄
第一步是創(chuàng)建一個(gè)簡(jiǎn)單的值對(duì)象以映射數(shù)據(jù)庫(kù)中的行–我將其稱(chēng)為T(mén)radeOrderRecord.java 。 這看起來(lái)很像原始域?qū)ο?#xff0c;只是我刪除了訪問(wèn)器和增變器以清楚地表明這是一個(gè)沒(méi)有行為的值對(duì)象。
為了使構(gòu)造這些記錄對(duì)象更容易,我使用了karg,這是我的一位同事編寫(xiě)的一個(gè)庫(kù)–這要求我們聲明可用于構(gòu)造記錄的一組參數(shù),以及一個(gè)接受參數(shù)列表的構(gòu)造函數(shù)。 這極大地簡(jiǎn)化了構(gòu)造,并且避免了我們擁有需要27個(gè)字符串的構(gòu)造函數(shù)(例如)。
@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);} }富域
我們的目標(biāo)是使TradeOrder成為一個(gè)豐富的域?qū)ο?它應(yīng)該具有與“ TradeOrder”的域概念相關(guān)的所有行為和數(shù)據(jù)。
數(shù)據(jù)
TradeOrder首先需要在內(nèi)部存儲(chǔ)與TradeOrder相關(guān)的所有數(shù)據(jù)(至少作為起點(diǎn),未使用的字段暗示我們可能能夠進(jìn)一步簡(jiǎn)化此操作)。
public class TradeOrder {private final String id;private final String currencyId;private final BigDecimal amount;我們使數(shù)據(jù)不可變 。 不可變狀態(tài)通常是一件好事–在這里,它迫使我們清楚這是一個(gè)完全填充的TradeOrder,并且由于它具有ID,因此它始終與數(shù)據(jù)庫(kù)中的一行關(guān)聯(lián)。 通過(guò)使TradeOrder不可變,顯而易見(jiàn)的問(wèn)題是–如何更新訂單? 嗯,我們可以選擇多種方式來(lái)做到這一點(diǎn)–但這是在不同時(shí)間的不同故事。
我們也不需要訪問(wèn)器。 由于我們計(jì)劃將與TradeOrder相關(guān)的所有行為放在TradeOrder類(lèi)本身上,因此其他類(lèi)不需要詢(xún)問(wèn)信息,它們只需要告訴我們他們想要實(shí)現(xiàn)什么。
注意:有一個(gè)(現(xiàn)在不建議使用)訪問(wèn)器–暗示應(yīng)進(jìn)一步移動(dòng)的行為。
依存關(guān)系
除了用于存儲(chǔ)數(shù)據(jù)的字段之外,TradeOrder還將具有表示可注入依賴(lài)項(xiàng)的字段。
private final CurrencyCache currencyCache; private final PriceFetcher bestPriceFetcher; private final PositionFetcher hedgeFundAssetPositionsFetcher; private final FXService fxService;有些人會(huì)發(fā)現(xiàn)這種冒犯性-將依賴(lài)項(xiàng)與數(shù)據(jù)混合在一起。 但是,就我個(gè)人而言,我認(rèn)為這種權(quán)衡是值得的–能夠在與之相關(guān)的類(lèi)上定義我們的行為的好處是值得的。
行為
現(xiàn)在我們將數(shù)據(jù)和依賴(lài)項(xiàng)全部集中在一處,在FiguresFactory的方法之間移動(dòng)相對(duì)容易:
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); }施工
我們需要解決的最后一件事是如何創(chuàng)建TradeOrder實(shí)例。 由于數(shù)據(jù)和依賴(lài)項(xiàng)的字段都標(biāo)記為final,因此構(gòu)造函數(shù)必須將它們?nèi)砍跏蓟?這意味著我們需要一個(gè)帶有依賴(lài)關(guān)系的構(gòu)造函數(shù)和一個(gè)TradeOrderRecord(即我們從數(shù)據(jù)庫(kù)中讀取的值對(duì)象):
@Inject protected TradeOrder(CurrencyCache currencyCache,PriceFetcher bestPriceFetcher,PositionFetcher hedgeFundAssetPositionsFetcher,FXService fxService,@Assisted TradeOrderRecord record) {... }這不是特別漂亮,但是要注意的關(guān)鍵是@Assisted注釋。 這使我們可以告訴Guice,其他依賴(lài)項(xiàng)已正常注入,而TradeOrderRecord應(yīng)該從工廠方法傳遞過(guò)來(lái)。 工廠界面本身如下所示:
public static interface Factory {TradeOrder create(TradeOrderRecord record); }我們不需要實(shí)現(xiàn)此接口,Guice會(huì)自動(dòng)提供它。 當(dāng)需要?jiǎng)?chuàng)建TradeOrder實(shí)例時(shí),可以在其他地方使用TradeOrder.Factory成為可注入依賴(lài)項(xiàng)。 Guice會(huì)像平常一樣初始化可注入依賴(lài)關(guān)系,輔助依賴(lài)關(guān)系– TradeOrderRecord –從工廠傳遞過(guò)來(lái)。 因此,我們的調(diào)用代碼無(wú)需擔(dān)心我們的富域需要可注入的依賴(lài)項(xiàng)。
@Inject private TradeOrder.Factory tradeOrderFactory; ... TradeOrderRecord record = tradeOrderDAO.loadById(id); TradeOrder order = tradeOrderFactory.create(record);結(jié)論
通過(guò)將依賴(lài)項(xiàng)和數(shù)據(jù)混合到一個(gè)豐富的域模型中,我們可以定義具有正確行為的類(lèi)。 現(xiàn)在,TradeOrder中明顯的代碼味道是創(chuàng)建圖形的詳細(xì)機(jī)制可能是一個(gè)單獨(dú)的問(wèn)題,應(yīng)予以分解。 沒(méi)關(guān)系,我們可以引入一個(gè)新的依賴(lài)項(xiàng)來(lái)進(jìn)行管理-只要TradeOrder仍然是構(gòu)造Figures對(duì)象的起點(diǎn)。
通過(guò)將行為放在一個(gè)地方,我們的域模型更易于使用,更易于推理,也更容易發(fā)現(xiàn)重復(fù)或相似的情況。 然后,我們可以使用良好的對(duì)象模型進(jìn)行合理的重構(gòu),而不是將任意的區(qū)別引入到作為函數(shù)庫(kù)而不是域參與者的幫助器類(lèi)中。
參考: 實(shí)施富域M 來(lái)自JCG合作伙伴 David Green的Guice先生在Actively Lazy Blog上發(fā)表的評(píng)論 。
相關(guān)文章 :- 在領(lǐng)域驅(qū)動(dòng)的設(shè)計(jì),貧乏的領(lǐng)域模型,代碼生成,依賴(lài)項(xiàng)注入等方面……
- 在域驅(qū)動(dòng)設(shè)計(jì)中使用狀態(tài)模式
- Spring和AspectJ的領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)
- Spring依賴(lài)注入技術(shù)的發(fā)展
- 什么是依賴(lài)倒置? 是IoC嗎?
翻譯自: https://www.javacodegeeks.com/2011/10/rich-domain-model-with-guice.html
總結(jié)
以上是生活随笔為你收集整理的带有Guice的富域模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: image 闪烁 c# ajax upd
- 下一篇: 简单的Twitter:Heroku上的P