带有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.mzph.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;一切正…