领域驱动设计模式设计与实践_在域驱动设计中使用状态模式

领域驱动设计模式设计与实践

域驱动设计(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 / setters,或者也许我们可以只使用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()会发生什么呢? 状态字段现在指向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.mzph.cn/news/359672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html 英文文字纵向排列,CSS几种简单方法实现文字竖向排版

1.一个句子的竖向排列如图:1.2. test.one {width: 20px;margin: 0 auto;line-height: 24px;font-size: 20px;}.two {width: 15px;margin: 0 auto;line-height: 24px;font-size: 20px;word-wrap: break-word;/*英文的时候需要加上这句,自动换行*/}我是竖列…

jstree 节点拖拽保存数据库

需要jstree具有拖拽功能需要在加载jstree时添加dnd插件,具体看代码: $(**).jstree({//plugins-各种jstree的插件引入,展示树的多样性 plugins : [ "dnd", "types", "wholerow" ], core : {"check_callbac…

html js utf8编码转换,js 编码转换 gb2312 和 utf8 互转的2种方法

方法一:function gb2utf8(data){var glbEncode [];gb2utf8_data data;execScript("gb2utf8_data MidB(gb2utf8_data, 1)", "VBScript");var tescape(gb2utf8_data).replace(/%u/g,"").replace(/(.{2})(.{2})/g,"%$2%$1").replace(/…

向其他进程注入代码的三种方法

如何向其他线程的地址空间中注入代码并在这个线程的上下文中执行之? 目录:●导言●Windows 钩子(Hooks)●CreateRemoteThread 和LoadLibrary 技术○进程间通讯●CreateRemoteThread 和 WriteProcessmemory 技术○如何使用该技术子…

自动添加html结束标志,HTML:包含或排除可选的结束标记?

MYYA我在这里添加一些链接来帮助您了解HTML的历史,以便您了解各种矛盾。这不是你的问题的答案,但在阅读这些各种摘要后你会知道更多。我们是怎么来到这里的? - 潜入HTML5网络历史HTML简史HTML的历史 - HTML WG WikiDive Into HTML5的一些摘录…

JAR清单类路径不仅适用于Java Application Launcher

自从我开始学习Java以来​​,我几乎已经知道, 清单文件中的Class-Path标头字段为可执行JAR (具有由另一个称为Main-Class清单指定应用程序起点的 JAR)指定相对运行时类路径。 一个同事最近碰到一个让我感到惊讶,因为它…

[原创]ActionScript3游戏中的图像编程(连载五)

总目录:http://www.cnblogs.com/iloveas/p/3879125.html 1.1.2 Flash中的ARGB模式与不透明度的关系 ARGB是Flash,svg等矢量处理软件特有的一种色彩模式,事实上我觉得它有点扯淡,A(alpha)不应该作为一个通道…

通过url,获取html内容,并解析,如何使用 JavaScript 解析 URL

在 Web 开发中,有许多情况需要解析 URL,这篇主要学习如何使用 URL 对象实现这一点。开始创建一个以下内容的 HTML 文件,并在浏览器中打开。JavaScript URL parsing// 激动人心的代码即将写在这里如果你想尝试本文中的任何内容,可以…

define 汉字 error C2001: newline in constant

这个问题真的很让我头大,搜了很多办法都不行,问题是我之前也遇到过,但是编码转为utf-8 unsignature就行了,这次把编码从gb转为utf-8 unsignature 却不行。于是想看看cocos2d-x库文件的编码格式,发现用的是utf-8&#x…

solaris安装java_Solaris是出色的Java开发平台的原因

solaris安装java几天前,我发布了“ OpenSolaris的死亡:为Java开发人员选择操作系统 ”,其中我说Solaris是Java开发人员的绝佳平台。 这篇文章的重点只是想知道自OpenSolaris淘汰以来我将使用哪个Solaris版本。 正如Neil的评论使我意识到的那样…

正确使用计算机说课稿,《计算机结构原理初步》说课稿

在教师招聘考试的过程中,高中信息说课稿的难度就在于如何处理理论与实践的关系,希望这篇《计算机结构原理初步》说课稿能给予你帮助。各位考官大家好!我是号考生,今天我说课的题目是《计算机结构原理初步》。现代教学理论认为,在教…

stringstream实例

stringstream的具体作用稍后来总结&#xff0c;这里分享一个实例&#xff0c;从txt文档中读取数据&#xff0c;并对进行处理。 #include <iostream> #include <sstream> //stringstream的头文件 #include <fstream> #include <vector> #include <s…

计算机2013知识,2013年全国计算机一级考试B基本知识点五

基础5单元格操作对已建立的工作表&#xff0c;根据需要可以编辑修改其中的数据首先要移动单元格指针到目的地或选定编辑对象&#xff0c;然后才能进行增、删、改操作。1.单元格指针的移动要编辑某单元格&#xff0c;必须把单元格指针移动到该单元格&#xff0c;使之成为当前单元…

两种解决IE6不支持固定定位的方法

有两种让IE6支持position:fixed1.用CSS执行表达式 *{margin:0;padding:0;} * html,* html body{ background-image:url(about:blank);background-attachment:fixed; } * html .fixed{position:absolute;bottom:auto;top:expression(eval(document.documentElement.scrollTopdoc…

smartgwt_高级SmartGWT教程,第1部分

smartgwt贾斯汀&#xff08;Justin&#xff09;&#xff0c;帕特&#xff08;Pat&#xff09;和我已经开始着手进行一个需要管理和管理用户界面的副项目。 在与SmartGWT和GWT共同工作了一段时间之后&#xff0c;我们决定使用SmartGWT创建接口。 我们非常喜欢视觉组件&#xff0…

计算机英语女人英语怎么说,英语时差:计算机和女人

00:0000:00微信扫码登陆&#xff0c;畅听全站所有音频&#xff01;(20秒后自动关闭)X关注后&#xff0c;点此关闭https://online2.tingclass.net/lesson/shi0529/10000/10183/67.mp3https://image.tingclass.net/statics/js/2012When you hear the term, "computer geek,&…

Python 生成账号密码算法

有个需求&#xff0c;需要伪造跟用户行为非常类似的账号密码&#xff0c;而且需要一个阀值控制伪造的数量。 在这需求上&#xff0c;还有一个就是需要控制生成的比率、跳出率不能过高或者太低。 对此就随手用python写了一个&#xff0c;bug不知道有木有&#xff0c;没有测&…

云计算系统是大规模计算机系统吗,云计算的系统架构及技术探析

云计算技术属于计算机技术的一种&#xff0c;是目前计算机技术中应用以及研究重点之一&#xff0c;那么云计算到底是什么呢&#xff1f;云计算是在并行处理&#xff0c;分析式处理等技术的基础上发展而来的新技术&#xff0c;可以有效的将计算机进行整合&#xff0c;建立新颖的…

mvc crud_Spring MVC3 Hibernate CRUD示例应用程序

mvc crud学习从HelloWorld应用程序开始的任何Web框架都是一个好主意。 一旦我们熟悉了框架配置&#xff0c;最好做一个CRUD&#xff08;创建&#xff0c;读取&#xff0c;更新&#xff0c;删除&#xff09;应用程序&#xff0c;该应用程序涵盖Web框架的各个方面&#xff0c;例如…

关于支付宝即时到帐异步通知(notify_url)一点总结

&#xff08;1&#xff09;首先做支付的商业网站&#xff0c;需要能够上网&#xff08;支付成功后&#xff0c;需要进行参数回传验证&#xff0c;如果上不了网&#xff0c;responseText就直接为false&#xff09;&#xff08;2&#xff09;notify_url这个不能进行验证&#xff…