这是半年前翻译的pojos in action,之前发在其他的blog,现在搬过来第二部分

6 提交订单――用户确定她要提交订单

<o:p></o:p>

用例中的每一段的第二部分描述了应用程序对请求作出的反应。应用程序的反应可以描述为一个职责的集合。比如说,应用程序这样执行输入发货信息的请求:

1 核实发货时间是在将来而且至少有一个餐馆提供发货信息

2 更新未提交订单的发货信息

3 显示可以提供服务的餐馆的列表

<o:p></o:p>

应用程序的职责可以划分为两种。第一种职责是检验或者确定用户的输入,计算结果,更新数据库。一般说来services或者entities必须定义履行这个职责的方法。第二种职责是显示值。尽管有责任来显示数据,但是领域模型却是有责任来提供数据的。一般说来,entities或者repositories必须定义返回所需值的方法。每一个职责都跟一个或多个领域模型的方法有协调关系,所以实现一个职责的第一步是定义方法,然后再把这些方法分配给类。

<o:p></o:p>

确定方法

一旦我们决定了请求和应用程序是如何响应这些请求的,下一步就是确定领域模型类为实现上述响应而必须提供的方法。如我们在图3.1中所看到的,当应用程序处理一个请求的时候,领域模型的客户端-一个表现层或者一个facade――会一次或多次调用领域模型来验证请求,执行计算,更新数据库。它也会通过调用领域模型来得到需要显示用户的数据。在动手写业务逻辑之前,我们必须确定领域模型客户端需要调用方法并且决定其参数,返回类型和其所属的类。

<o:p></o:p>

对每一个请求来说,我们一般会定义一个服务方法来做批量的工作,包括验证请求,执行计算,更新数据库。我们也要定义其他的entityrepository方法来返回显示的数据。这些事是怎么做的呢,让我们来确定领域模型必须定义的用来处理输入发货信息请求的方法food to go程序用两步来处理这个请求。首先,它必须检验发货信息,然后更新未提交的订单。然后,它必须显示可用餐馆的列表。

让我们考虑一下每一个职责。

<o:p></o:p>

一个职责是属于业务逻辑层,因为它

领域模型客户端可以直接调用PendingOrder来验证和保存发货信息。但是如我早先所提到的,一个领域模型业务(就是把业务逻辑定义在领域模型中)对处理请求来说是一个更好的选择,因为它提供更高级别的封装而且能把更多的业务逻辑移到领域模型中,这样就简化了领域模型的客户端。

<o:p></o:p>

这个领域模型没有任何的业务,所以我们需要定义一个。最简单的做法是给place order用例定义一个叫做PlaceOrderService的业务类。它有一个updateDeliveryInfo()方法,这个方法的作用是检验是否至少有一个餐馆提供发货信息中描述的发货服务:

<o:p></o:p>

public interface PlaceOrderService{

PlaceOrderServiceResult updateDeliveryInfo(String pendingOrderId,

Address deliveryAddress,

Date deliverTime);

}

<o:p></o:p>

public class PlaceOrderServiceResult{

private int statusCode;

private PendingOrder pendingOrder;

}

<o:p></o:p>

这段代码pendingOrderId和发货信息作为参数。pendingOrderId参数是数据库PendingOrder表的主键,而且被存储在表现层的HttpSession或者浏览器中。deliveryAddressdeliveryTime参数包含了用户输入的的值。

<o:p></o:p>

updateDeliveryInfo()方法返回一个PlaceOrderServiceResult,它包含了一个状态码和PendingOrder。状态码表示验证发货信息是否合法的结果。这个方法返回一个PendingOrder因为调用者需要它。比如说,由表现层把它(PendingOrder)显示出来。

程序在处理输入发货信息这个请求时的另一个职责时显示提供服务餐馆的列表。这个职责主要属于表现层,因为它包含了要显示的数据。然而,领域模型必须提供一个能找到可提供该服务的餐馆的方法。找该类餐馆是一个数据库查询,该查询repository封装。

<o:p></o:p>

因为我们在要找到餐馆,所以添加一个RestaurantRepository到领域模型中是完全有意义的,而且要使该类有责任得到提供服务的餐馆的列表。我们定义了一个findAvailableRestaurants()方法,该方法以发货信息为参数并且返回提供发货信息中描述的服务的餐馆列表:

<o:p></o:p>

public interface RestaurantRepository{

List findAvailableRestaurants(Address deliveryAddress,

Date deliveryTime);

}

<o:p></o:p>

另外,因为表现层会显示餐馆的名字和类型,所以Restaurant类必须定义getter方法来返回这些值。

<o:p></o:p>

public class Restaurant{

public String getName(){}

public String getType(){}

}

<o:p></o:p>

getName()方法返回餐馆的名字,getType()方法返回餐馆的类型。

<o:p></o:p>

表现层或者facade首先调用PlaceOrderService来更新pendingOrder然后调用RestaurantRepository来得到可以提供服务的餐馆。PlaceOrderService不返回提供服务餐馆的列表,因为,如果它这么做了,那么它就紧紧的和ui界面设计联系在一起了。最好是把业务逻辑与ui界面分开并且让领域模型客户端来对领域模型作额外的调用以得到需要显示的数据。facade或者表现层调用领域模型的业务逻辑处理来更新领域模型并且调用repositories来得到要显示用户的数据。要记住:领域模型通过本地调用而被调用而且和多重调用没有更向上层的联系

<o:p></o:p>

正如你所看到的那样,我们可以分析一个用例并且确定被领域模型客户端调用方法。我们可以使用这以过程来分析place order用例的其他步骤并且确定附加的方法。一旦你已经确定了这些方法,下一步就是实现他们。

<o:p></o:p>

用测试驱动开发来实现方法

<o:p></o:p>

在开发过程中的这一步上,我们已经有了由PlaceOrderServiceRestaurantRepository接口指定的方法而且还有一些Restaurant类的简单的getter方法。现在我们需要实现这些方法。要实现这些方法有很多途径。我最喜欢的途径是使用用例驱动开发,这是一个步正式的,以代码为中心的,发展很快的技术。当使用TDD的时候,你首先要为新功能编写自动化的单元测试用例。然后再写实现功能代码而且要确保测试通过。

<o:p></o:p>

举个例子,当使用TTD来实现一个业务方法比如说PlaceOrderService.updateDeliveryInfo(),你从写一个或多个测试用例开始。每一个测试使用一个特殊的参数的组合来调用方法并且验证它是否正确的更新了pendingOrder,是否返回了预期的结果。写完测试之后,你要实现业务方法并且使它们通过测试。TDD输出正在工作,正在测试代码自动化的测试用例。除了确保代码工作之外,测试证明了应用程序预期的行为。

<o:p></o:p>

为了成功的使用TDD,你需要一个能为微小变化提供立即反应的开发环境。写一个测试并且使之通过这种事一天要发生很多次。重构,一个改善设计的过程,一个我们马上将要描述的过程,也由程序的小改变和测试组成。结果,每两分钟或者更少的时间内你就需要作一个编辑-编译-debug的循环动作。如果你想要高生产率,你就不能等到ejb被部署或者数据库被重建。正如你能看到的,TDD和轻量级框架技术能够很好的配合工作。

<o:p></o:p>

重构你的代码的重要性

<o:p></o:p>

TDD很大程度上不同于那些很多的自顶开始设计的开发技术,因为当设计不断增长的时候就需要更多的测试。但是发展一个设计的风险是你可能以一个未组织的混乱状态结束。为了阻止这种事情的发生,定期的重构代码是很重要的。

<o:p></o:p>

重构是在不改变应用程序行为的前提下的对设计的改善的过程,一旦为新的功能准备的测试通过了重构也就完成了。重构技术的示例包括方法提取重复的代码,引入超类来实现通用的行为。重构代码一个好的途径是作一系列的小变化并且每个小变化之后都要运行测试用例。重构是TDD的精华部分,它将帮助你开发出设计上很优秀的程序。

<o:p></o:p>

使用JUnit的优点

<o:p></o:p>

当从草稿到写测试代码成为某种可能时,这真是一个很少有的好主意。一个更好的方法是使用测试框架,如JUnit,它提供的类能够使你的开发和运行测试变得更容易。它处理异常并且报告测试失败信息;它提供使用断言的方法JUnit提供的方法),这些断言是用来断言调用方法所得到的结果的;它能够让你把测试组织进测试套件的层次。另外,IDE,如Eclipse提供了一个GUI来运行JUnit测试。当然也有多种JUnit的扩展提供了额外的特性,如JMock,我们将在后面一点讨论它。如果要更了解JUnit,请参考JUnit in Action一书。

<o:p></o:p>

mock对象来简化和加速测试

<o:p></o:p>

我是TDD一个大粉丝而且相信:如果你想在没有混乱和花漫漫长夜来跟踪bug的情况下成功的开发软件,严格的自动化测试是要点。但是写测试是很困难的因为所有的setup代码都需要你自己写。而且,如果你写了很多的测试,他们运行起来要花很多的时间,尤其是如果他们是在访问数据库的时候。在我参与的两个项目中,当测试代码越来越多的时候,程序最终需要花30分钟才能把测试跑完。这听上去好像不是一段很长的时间,但是它是失败的一个很大的根源,因为这种失败能减慢开发速度因为每个人都需要先跑测试然后才能check in代码

<o:p></o:p>

为什么一个类的测试很难写而且执行速度慢呢,一个主要原因是因为它的协作者。大多数类都不是孤立的而且用一个或多个其他类来代替其协作者。举个例子,稍后,你将看到PlaceOrderService是如何来调用几个其他的领域模型类的,包括PendingOrderRestaurantRepository,和PendingOrderRepository。协作通常是一件好事因为它能使类保持小体型。如果类需要访问外部的资源(比如说数据库)协作也是基本的,因为为了访问数据库,测试程序必须使用其他的类,比如说jdbc提供的类。但是协作可以使测试变得很困难:

<o:p></o:p>

1 自顶向下开发测试是很难的―――你必须在编写你的任何一个类的单元测试之前实现该类的协作者。这样,比如说,就使在实现如PlaceOrderService这些类之前开发和测试一个业务逻辑类变得不可能了,而PlaceOrderService又需要在领域模型实现之前调用它。我们被强制的投入到领域模型细节的开发中去了。

<o:p></o:p>

2 创建和初始化协作者使一个类的测试变得更加复杂―――一些对象为了测试需要复杂的初始化过程来使它们进入正确的状态。比方说,如果我们需要测试一个特定的情节,PlaceOrderService.updateDeliverInfo()调用,以一个未提交到数据库但是已经被用户确定提交的订单id作为参数,我们将不得不调用PendingOrder中的多个方法来使之进入提交状态。这样使得写测试变得更加困难。

<o:p></o:p>

3 协作者引入了不受欢迎的耦合

<o:p></o:p>

举例来说,使用repositories的真实的实现将把领域模型和数据库耦合起来并且强迫我们忙于持久化上的问题。在这一点上,这样做将比我们要解决的问题还要复杂。此外,访问数据库可以减低测试的速度。

<o:p></o:p>

<o:p></o:p>

幸运的是,我们可以使用mock对象来解决这些问题。一个mock对象是一个假的实现以用来为测试单独地使用。一个测试将会配置一个mock对象来期待某个方法调用并且返回预期地值。如果预期的值和实际的值不匹配的话,mock对象会抛出一个异常。使用了mock对象,我们就可以模拟领域对象的协作者而不用真的去实现他们。同时,当模拟repositories时,我们不需要去处理持久化问题而且可以写出没有数据库也可以运行的测试程序。使用mock对象允许我们简化其他复杂的对象交互,使我们可以把注意力在同一时间只集中在程序的一个部分。

<o:p></o:p>

如果你想测试的类通过一个接口调用一个协作者,那么实现mock对象的一个方法就是简单的定义一个实现该接口的类。对PlaceOrderService的测试用例,它使用了RestaurantRepository接口,该接口的方法应该返回测试值。尽管这样的做法在简单的测试中很有效,但是写伪装类的工作很容易变的枯燥乏味。而且,如果有一个接口需要实现你就只能使用该方法

<o:p></o:p>

实现mock对象的一个更好的方法是使用mock对象测试框架。他们不但使写测试变得简单,而且也支持模仿具体的类。有好几个mock对象测试框架,他们有,EasyMockjMockEasyMockjMock都是JUnit框架的扩展,并且提供创建和配置mock对象的类。

<o:p></o:p>

让我们来看看简单的例子中是如何使用jMock的,jMock是我个人最喜欢,因为它看上去比其他类似的框架更有弹性。想象一下,你需要为PlaceOrderService.updateRestaurant()方法一个测试,需要调用RestaurantRepository.findRestaurant()方法。你不需要手工去编写一个假的RestaurantRepository类,你可以使用jMock来创建一个假的RestaurantRepository并且设定它来使之预定调用它的findRestaurant()方法。如果调用了一些其他没有预期指定要调用方法,或者预期指定要调用方法没有被调用,那么jMock将会抛出异常。

3.1显示一个PlaceOrderService类的测试用例的片断:

<o:p></o:p>

<v:shapetype id="_x0000_t75" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" filled="f" stroked="f" coordsize="21600,21600" o:spt="75"><v:stroke joinstyle="miter"></v:stroke><v:formulas><v:f eqn="if lineDrawn pixellinewidth 0"></v:f><v:f eqn="sum @0 1 0"></v:f><v:f eqn="sum 0 0 @1"></v:f><v:f eqn="prod @2 1 2"></v:f><v:f eqn="prod @3 21600 pixelWidth"></v:f><v:f eqn="prod @3 21600 pixelHeight"></v:f><v:f eqn="sum @0 0 1"></v:f><v:f eqn="prod @6 1 2"></v:f><v:f eqn="prod @7 21600 pixelWidth"></v:f><v:f eqn="sum @8 21600 0"></v:f><v:f eqn="prod @7 21600 pixelHeight"></v:f><v:f eqn="sum @10 21600 0"></v:f></v:formulas><v:path o:connecttype="rect" o:extrusionok="f" gradientshapeok="t"></v:path><o:lock aspectratio="t" v:ext="edit"></o:lock></v:shapetype><v:shape id="_x0000_i1025" style="WIDTH: 486pt; HEIGHT: 459pt" type="#_x0000_t75"><v:imagedata o:title="l3" src="file:///C:\DOCUME~1\azhang\LOCALS~1\Temp\msohtml1\01\clip_image001.jpg"></v:imagedata></v:shape>

<o:p></o:p>

1 PlaceOrderServiceTests类继承了jMock框架提供的MockObjectTestCase

<o:p></o:p>

2 setUp()方法创建了假的RestaurantRepository

<o:p></o:p>

3 得到jMock创建的代理,该代理实现了RestaurantRepository接口

<o:p></o:p>

4 setUp()方法创建了PlaceOrderServiceImpl,把上面的代理传递给它的构造方法

<o:p></o:p>

5 testUpdateRestaurant_good()方法配置了假的RestaurantRepository来计划调用它的findRestaurant()方法调用时把restaurantId传递给该方法并返回一个测试的Restaurant

6 测试调用service方法,该方法调用假的restaurant

<o:p></o:p>

jMock提供的两个关键的类是MockMockObjectTestCaseMock类是用来创建一个mock对象的,这个mock的行为应该和传递给Mock()的接口或者类的实例的行为相同。一个测试用例可以通过调用proxy()方法和把返回结果看成正确的类型来访问mock对象。mock对象的预期行为可以通过调用Mock类的多种方法来定义,包括Mock.expect()方法一个mock对象如果调用了没有预期指定要调用方法,那么它将抛出异常。另外,一个测试用例可以检验所有预期指定要调用方法是否已经被调用,这是通过调用Mock.verify()方法来实现的,如果预期指定要调用方法没有被调用,该方法将抛出异常。

<o:p></o:p>

MockObjectTestCaseJUnit类的子类,前者是用来写mock对象的测试用的。它提供了几种方便的方法来配置预期行为,包括eq()returnValue()。此外,它会自动的在任何类型的Mock调用verify()方法并且检验所有的预期指定要被执行的方法是否被调用了。

<o:p></o:p>

使用一个mock对象框架(如jMock)可以让你在自顶向下的方式中从servicerepository接口的方法开始来实现一个领域模型,这些方法是以需求为驱动力的。在实现了领域模型和其测试类之后,我们就已经确定了它的协作者需要实现的方法。我们可以为每一个类重复以上过程。这个过程一直重复着,直到所有的类和方法都被实现了。在这个过程的最后,我们就拥有了一个pojo组成的可执行的可测试的领域模型了。让我们来看一下这是一个什么样的步骤。

<o:p></o:p>

3.3 实现一个领域模型:实例

<o:p></o:p>

在这一部分你将看到如何使用前面描述的技术来开发一个领域模型的实例。我将告诉你如何用这样的技术来实现一个方法,该方法是用来处理输入发货信息请求的。我首先实现了我们先前确定的PlaceOrderServiceupdateDeliveryInfo()方法。然后,我会告诉你如何实现PendingOrder一个方法,该方法会被updateDeliveryInfo()方法调用。最后,结果是工作的而且测试PlaceOrderServicePendingOrder方法检验了发货信息的合法性而且更新了PendingOrder。需要的repository方法也被确定下来了。学习了这个小例子之后,你将学会一个能有效的开发和测试领域模型的方法。因为repository方法需要调用持久框架,所以我们会到46节才实现这些方法

<o:p></o:p>

3.3.1实现一个领域业务方法

<o:p></o:p>

PlaceOrderService,是一个领域模型中的业务类,有一个updateDeliveryInfo()方法,当用户输入发货信息时该方法会被调用,这个方法有如下的特点:

<o:p></o:p>

public interface PlaceOrderService{

<o:p></o:p>

PlaceOrderServiceResult updateDeliveryInfo(String pendingOrderId,

Address deliveryAddress,

&nbs

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图:提供一种方法顺序访问一个聚合对象中的每个元素,而又不想暴露该对象的内部表示。应用:STL标准库迭代器实现、Java集合类型迭代器等模式结构:心得:迭代器模式的目的是在不获知集合对象内部细节的同时能对集合元素进行遍历操作
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:(1)同步阻塞IO(BlockingIO):即传统的IO模型。(2)同步非阻塞IO(Non-blockingIO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的N
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定义一系列算法,把他们封装起来,并且使他们可以相互替换,使算法可以独立于使用它的客户而变化。应用:排序的比较方法、封装针对类的不同的算法、消除条件判断、寄存器分配算法等。模式结构:心得:对对象(Context)的处理操作可
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个作用于某对象结构中的各元素的操作,它使你在不改变各元素的类的前提下定义作用于这些元素的新操作。应用:作用于编译器语法树的语义分析算法。模式结构:心得:访问者模式是要解决对对象添加新的操作和功能时候,如何尽可能不修改对象的类的一种方
命令模式(Command)命令模式(Command)[Action/Transaction]意图:将一个请求封装为一个对象,从而可用不同的请求对客户参数化。对请求排队或记录请求日志,以及支持可撤消的操作。应用:用户操作日志、撤销恢复操作。模式结构:心得:命令对象的抽象接口(Command)提供的两个
生成器模式(Builder)生成器模式(Builder)意图:将一个对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 应用:编译器词法分析器指导生成抽象语法树、构造迷宫等。模式结构:心得:和工厂模式不同的是,Builder模式需要详细的指导产品的生产。指导者(Director)使用C
设计模式学习心得《设计模式:可复用面向对象软件的基础》一书以更贴近读者思维的角度描述了GOF的23个设计模式。按照书中介绍的每个设计模式的内容,结合网上搜集的资料,我将对设计模式的学习心得总结出来。网络上关于设计模式的资料和文章汗牛充栋,有些文章对设计模式介绍生动形象。但是我相信“一千个读者,一千个
工厂方法模式(Factory Method)工厂方法模式(Factory Method)[Virtual Constructor]意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实力化延迟到子类。应用:多文档应用管理不同类型的文档。模式结构:心得:面对同一继承体系(Produc
单例模式(Singleton)单例模式(Singleton)意图:保证一个类只有一个实例,并提供一个访问它的全局访问点。应用:Session或者控件的唯一示例等。模式结构:心得:单例模式应该是设计模式中最简单的结构了,它的目的很简单,就是保证自身的实例只有一份。实现这种目的的方式有很多,在Java中
装饰者模式(Decorator)装饰者模式(Decorator)[Wrapper]意图:动态的给一个对象添加一些额外的职责,就增加功能来说,比生成子类更为灵活。应用:给GUI组件添加功能等。模式结构:心得:装饰器(Decorator)和被装饰的对象(ConcreteComponent)拥有统一的接口
抽象工厂模式(Abstract Factory)抽象工厂模式(Abstract Factory)[Kit]意图:提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。应用:用户界面工具包。模式结构:心得:工厂方法把生产产品的方式封装起来了,但是一个工厂只能生产一类对象,当一个工厂需要生
桥接模式(Bridge)桥接模式(Bridge)[Handle/Body]意图:将抽象部分与它的实现部分分离,使他们都可以独立的变化。应用:不同系统平台的Windows界面。模式结构:心得:用户所见类体系结构(Window派生)提供了一系列用户的高层操作的接口,但是这些接口的实现是基于具体的底层实现
适配器模式(Adapter)适配器模式(Adapter)[Wrapper]意图:将类的一个接口转换成用户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。应用:将图形类接口适配到用户界面组件类中。模式结构:心得:适配器模式一般应用在具有相似接口可复用的条件下。目标接口(Targ
组合模式(Composition)组合模式(Composition)意图:将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。应用:组合图形、文件目录、GUI容器等。模式结构:心得: 用户(Client)通过抽象类(Component)提供的公用接口统一
原型模式(Prototype)原型模式(Prototype)意图:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象。应用:Java/C#中的Clonable和IClonable接口等。模式结构:心得:原型模式本质上就是对象的拷贝,使用对象拷贝代替对象创建的原因有很多。比如对象的初始化构
什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。