来自
Rational Edge:这是“从用例到代码”系列文章中的第二部分,讨论如何把从用例中捕获的需求转换成可实现的表达形式与代码,本文介绍了在Rational
Unified Process(RUP)中进行用例设计的几个步骤,其结论是与具体的实现技术相关的。
本文是系列文章的第二部分,讨论把从用例中捕获的需求转换成可实现的表达形式与代码。在第一部分“用例分析”中,我逐步介绍了在Rational
Unified Process(RUP)中进行用例分析的几个主要步骤 1 。我引入了一个简单的车辆租借系统的案例研究(以下称为车辆调度),这个系统为客户提供浏览访问服务,客户可以预约车辆、取消预约、查看租借记录等等。我还给了一个简单的用例预约车辆,首先是一个通用版本,然后是一个补充版本,引入了更多的外部交互,在这个用例里这种交互是一定会发生的。
之后我用了简单的语法分析技术来确定用例中的候选实体,并用四个问题来考察它们。这些问题从四十一个候选实体中筛选出八个,它们通过测试而成为分析类。然后我确定了每个分析类的职责,这样在我们对每个类的定义中就有了James
Rumbaugh等人称之为“易碎边界”("crisp boundary")的东西。
确定了八个分析类之后,我引入了四个步骤用来构造初始分析类图,以捕获类间的关系和多样性。接着我又构造了预约车辆用例主场景的分析级别的序列图。它被严格约束于分析级别,只包含分析类,不包含设计类或实现类。为了使序列图更易理解,我引入了一般的用例控制器对象来为消费者参与者收到的信息提供中介。借助这些分析类及其已确定的关系,我又给每个分析类配上与其职责相一致的属性(数据成员)。最后,我为这个小的案例研究确定了分析机制。
在本系列的第二部分,我们要按步骤执行RUP中的用例设计活动。
RUP中用例设计的元素
图1选自RUP的《分析与设计概述》。它说明在其他分析与设计活动的上下文中,用例设计活动在哪里发生。
图1:RUP中的用例设计活动
在RUP中用例设计的目的是:
- 用交互改进用例实现
- 改进关于对设计类操作的需求。
- 改进关于对子系统及/或其界面操作的需求。
换个说法,用例设计的目标是把系统(也就是分析类)中每个业务抽象都转换成一个或多个作为可实现表示的设计类,并考虑到目标执行环境的属性。注意设计类是可实现的表示。设计类的实现(即编码)在设计稳定下来以后开始进行(虽然我们有时编写代码来研究可选的设计)。在设计时,我们要以合适的细节表达怎样着手于实现才能使编程实际上成为只跟语言和平台打交道的问题。图2说明了组成用例设计的步骤。
图2:用例设计步骤
设计活动包括同时保持关于你所建造的软件系统的多种视角。好的设计人员能成功地维系对目标系统的各种不同甚至对立的视图。正如一个长方体有六个面,彼此独立又都是同一个整体的部分,软件系统的设计也是多方面的,它包括:逻辑、流程、实现、配置、操作方面的视图;系统与软件架构;组件和类;模式与界面;静态结构与交互,等等。这篇文章里我不打算覆盖所有的软件设计实践,但我要给出一些具体的例子,解释我们在用例设计的主要步骤里究竟能做些什么,如图2。
类
面向对象和基于组件的设计策略的焦点在于类——将数据和对该数据操作的函数捆绑在一起的命名单元。在设计阶段,我们描述类以及类与其他类或子系统的关系,并且要把所有的技术关注点都考虑进来。
这些关注点有:
- 怎样表示两个类之间一对多或多对多的关系?
- 怎样表示对数据库的读写访问?
- 类里的哪些部分应该对其他类的对象可见?
- 哪些部分应该对其他类不可见?
- 怎样给多重对象提供一个简化界面?
- 怎样在业务类中把业务行为跟运行系统需要的技术行为合理地分开?
设计模型
用例设计活动的结果将产生RUP设计模型的内容,包括(当然不止于此):
- 系统需要的设计类(也就是分析类加技术类)。
- 在设计类中软件架构文档(SAD)内容的实现。
- 每个设计类将拥有的操作和属性。
- 对实际数据类型、初值或者特殊子系统或卖方提供软件的界面做出规约。
- 按子系统或架构类型划分系统功能。
- 用交互图(即协作图与序列图)指出设计类如何协作,以完成用例中捕获的业务流程。
设计模型将成为原料,我们在实现阶段把它转化成可执行代码。我重申一下,这里讨论的焦点是使用C#,Java,C++语言纯粹以面向对象的方法开发一个新系统(常称为“绿地”开发)。
我们要讨论的步骤
我在做用例设计活动时遵循图2所示的步骤,不过我另外加了一步,见下面的修订列表:
- 创建用例实现
- 描述设计对象间的交互
- 用子系统简化序列图(可选)
- 描述持续相关行为
- 定义设计机制(RUP中正常情况下是一个软件架构活动)
- 改进事件流程描述
- 统一类与子系统
- 评估你的结果
这些步骤并非一成不变,记住这一点很重要。根据你对你所设计的领域的理解,你使用RUP的经验,你对所用模型的个人偏好,或者你在刻划设计类时遵循的隐喻(例如,应用设计模式,或遵循一些原则如关注点分离或界面隔离原则),你遵循的序列会有所不同。重要的是要对你最终实现的解决方案完成一个全面的表述。
在上述各步骤,我们只是来仔细推敲一下下面这五步:
- 创建用例实现
- 描述设计对象间的交互
- 用子系统简化序列图(可选)
- 描述持续相关行为
- 定义设计机制
架构准则
因为设计与架构关系紧密,并且被架构很强地约束,我们先来考虑一下软件架构(SA),设想一下我们这个预约系统的全面架构,先约定SA已经作出了软件架构文档(SAD),指出架构必须支持以下技术准则:
- 可扩缩性。车辆调度的预约系统和RDBMS(关系数据库管理系统)必须支持多重、并发的客户会话。各个会话之间没有依赖关系,系统必须能毫无困难地扩展到几千个并发客户。
- 可维护性。软件组件的更新必须易于集中完成,绝不向表示层分布业务逻辑。
- 技术简单性。我们这个租车公司不打算培训或者雇用熟练使用象J2EE或.NET这样庞大的组件平台的人员。这对我们项目的需要来说是杀鸡用牛刀。现有的雇员已经熟悉Java、Java
ServerPages、servlets和JavaScript。
- 权衡技术。Internet必须成为浏览器和服务器之间的通信媒介,系统也必须把已有的编程界面用在遗留的平台上。
给定了这些准则,并且已知车辆调度项目在人员配备上是Java和servlet的开发人员,SA给SAD加上简单的UML架构模型,如图3。
图3:电子商务系统的初始配置图
图3解释了在独立客户浏览器与遗留预约数据库管理系统(DBMS)之间布局的最简单的视图,该DBMS包含了预约、客户和车辆的信息。图4给出了更多的内部细节,以及流程内部的一些通信。
图4:高级物理架构图
图4反映了我们这个应用程序的高级结构是基于JavaServerPage (JSP) Model 2的架构。客户用客户计算机上的浏览器完成交互。浏览器通过HTTP(超文本传输协议)与Web服务器通信,调用Web服务器上的servlet。我只是特别地讲了一下预约车辆servlet。该servlet以控制器的角色出现,协调预约车辆用例工作中需要完成的各个步骤。该servlet创建业务对象并与之交互,这些业务对象有预约、客户、保护产品等等,都是JSP要用到的。业务对象(将作为JavaBeans实现)通过一个Java数据库连接(JDBC)协议与接口,从遗留的关系数据库管理系统(DBMS)获得其内容。该servlet将服务请求转发给合适的JSP。
用例设计第1步:为每个用例创建用例实现
在本系列文章的第一部分,我指出用例实现实际上是一个收集若干UML图与其他文本工件的过程,这样的文本工件包括RUP的用例实现规约文档,它一并确认了我们已经拥有足以提供用例流程中的行为的类、责任和对象交互。
在分析阶段,我们的用例实现只包括分析类及分析对象,它们能组装成不同的UML图如类图和交互图。在设计时,我们的实现将包括设计级别的信息,以解释用例的各个步骤是如何用于设计对象的协作的。来自需求的类图、交互图与描述将组装成我们的设计级别的实现。如果你使用建模工具如Rational
Rose或者Rational XDE产品,建立用例实现可能只需要简单地在工具里创建一个用例模型元素,然后在其之下创建UML协作与交互图就可以了。如果你不用建模工具,你的实现就得在桌子上画出图来,不管是纸面手工作图还是在白板上画好模型拍下来。确认并命名一个用例实现保证了交互图和文本注释回到用例的可追踪性。本文余下的内容主要是讨论设计级别用例实现的内容。
用例设计第2步:描述设计对象之间的交互
这是复杂的一步,所以要引用文中几张不同的图,还要观察运行时的迭代流程。首先,注意在本系列文章第一部分“用例分析活动”那一段,根据车辆租借系统的属性我们作出了分析类图,如图5:
图5:分析级别的类图
这对分析类以及不同类的关系是一个适当的初始描述,但并未变成可实现的形式。比如,我们怎样在设计时表达一个给定的预约必须能访问0或更多个ProtectionProduct对象?VehicleInventory和Vehicle的关系是聚合(aggregation),而RentalLocation和VehicleInventory的关系是合成(composition)这又是什么意思?我们对Vehicle的status和specialEquipment属性,以及Reservation的dates、times和estimatedCost属性应该指定怎样的数据类型?在类里面应该添加怎样的操作?怎样表达Reservation(预约)RDBMS的接口,使我们能够对Reservation或Vehicle的状态发出请求或存储变更?
在解决这些问题之前,我们可以回顾一下第一部分中描述过的为登记顾客(Check in a Passenger)用例所作的分析级别的序列图(SQD),如图6。
分析级别的序列图(SQD)
查看大图,点屏幕右下角的四箭头图标放到最大。
在用例分析阶段,我们的目标是证明可行性,也就是业务对象拥有正确的职责来执行预约车辆用例的步骤。我们假定所有对象都是静态的,忽略对象的创建和析构,以简化任务。但在设计阶段,我们必须显式地说明对象的创建(以及析构,这一点与所用的程序设计语言有关)。我们还要问“谁负责”访问和“钩住”服务一条客户请求所需要的对象。
下面我们来讨论如何把这一序列图转化成关于对象交互的设计视角。还是从预约车辆用例开始,尤其是"happy
path"场景,那里一切正常。
这个预约车辆用例是怎么真的开始呢?图7的序列图片段捕捉了一个可能发生的对象间的交互。(简单起见,我在该序列图中省略了客户概况的处理,它放在了分析序列图当中。)
图7:第一部分中预约车辆的设计序列图
查看大图,点屏幕右下角的四箭头图标放到最大。
第1步,客户访问车辆调度网站,主页在客户的浏览器上可见,提供若干输入字段让客户填写,包括日期、地点、车辆类别。第2步,客户要求系统查询满足这些条件的可用车辆,JSP把表单数据发送给第3步中的预约(Reservation)servlet
(RsvnServlet)。预约servlet必须校验所选的租车地点与客户指定的日期时间相符,这样servlet在第4步建立一个空的RentalLocation对象,在第5步引导这个对象跟经确认的指派地点的数据组装在一起。RentalLocation必须从在线的预约RDBMS获得这些已有信息,在第6步我加入一个新的设计类DataAccess,来实现Facade模式。将类或组件以一个简化界面替代,该模式可以使我们将复杂的界面隐藏起来。RentalLocation对象object调用DataAccess对象的retrieveLocation(locationID)操作,返回一个结构化的数据组。在第7步RentalLocation调用一个自身的私有操作fill(dbRecord),解析数据组并为其适当的实例变量指派字段。在第7步结束时,RentalLocation的实例已经为一个特定的租车地点完全组装好了数据。在第8步servlet通过validate(dates)操作询问这个特定的租车地点:“在这几个日期时间中你是否可用?”在我们讨论过的"happy
path"场景中,RentalLocation回答“是”(OK)。
该地点在指定日期时间可用,因此servlet得到一个符合客户请求的车辆列表,并显示给客户。在第9步,RsvnServlet创建一个空的实例VehicleInventory
来保留符合客户要求的可用车辆。在第10步,servlet调用VehicleInventory的populate(
)操作传递所选条件。因为位置清单和可用车辆的信息位于在线数据库当中,在第11步VehicleInventory调用DataAccess对象的retrieveVehicles(collection)方法。这个方法知道去创建一个空的Vehicle对象(第12步),以从公司的RDBMS(未知)获得车辆信息,然后在第13步调用刚刚创建的Vehicle对象的fill(dbRecord)操作。在fill(dbRecord)中Vehicle对象解析从dbRecord(一个数据组)获得的数据,填进自己的实例变量。第14步,DataAccess对象把Vehicle对象填好后加入VehicleInventory。重复第12-14步,直至结果集中所有匹配的车辆都排进对象里。最后,VehicleInventory和servlet被告知其请求已经完成。
系统现在给客户显示所有可用的匹配车辆。在第15步和16步,预约servlet把VehicleInventory对象存进Session对象,使下一个JSP能把它取回,然后在第17步调用RequestDispatcher.forward(
)操作显示ShowInventory JSP。
在这一个小片段的设计序列图上,我们就碰到了几乎跟整个分析序列图一样多的信息。但在我们演进设计序列图时,我们确已说明了分析对象和技术对象将怎样产生交互,以完成预约车辆用例的工作。我们已经决定VehicleInventory只是一个集合类,保留多重车辆对象,如果我们继续这个序列图,我们会加上另一个集合类,以保留所有能指派给一个Reservation的ProtectionProduct对象。
根据在开发预订车辆序列图的这一部分时发现的新的技术类与新的操作,我们现在可以重构类图(回顾图5),如图8
所示。
图8:由操作更新的第一个版本的类图
我们回来看一下开发中的大图。如图7所示,我们采纳了Java Server Page Model 2架构的方法。这种方法是分离关注点原则的一个变种,附在模型-视图-控制器架构中。在图7左边,有两个JSP,它们是介绍页面或视图,在浏览器中显示。在序列图的中间是预约servlet,作为控制器它掌握客户预约车辆时需要执行的所有步骤。右边是实体对象,称为模型对象,它们有我们需要的业务知识和业务关系。
在开发图7的序列图片段时,我们在三个模型类Vehicle、VehicleInventory和RentalLocation中发现新的操作。至此,它们还只是从在线数据库获得数据的操作。当我们进一步开发这个序列图的后续部分,或开发其他用例的序列图时,在所有的类中都将发现新的操作,包括存取和计算等等。
所以我们不要到此为止。我们只看到了从在线数据库获取已有数据放进对象的行为。当客户选定某一辆车并预约时,租车流程的下一部分就开始了。图9描述了这一部分流程的序列图。
图9:预约车辆第二部分的设计序列图
第1步,客户指出他要预订某一辆车。该请求发送给servlet,servlet令VehicleInventory将这辆车(记为vehicleID)暂时标记为不可用的。这样标记可以防止其他租车客户预约同一辆车。VehicleInventory将该请求转发给对应客户选定车辆的Vehicle对象。VehicleInventory通过mark(expiry)操作完成这一点,其中expiry参数为保留给客户的最长时间,以应付他未完成预约就离开页面的情形。
注意这里我加入了UML注记(“也更新数据库状态吗?”)作为敏捷开发的身体力行者,我强烈建议你只对有长期价值的模型使用建模工具。但如果你的长期模型仍然有很多待解决的问题,不要太担心把那些问题放进模型里。你不见得经常需要这样做,但重要的是让问题对所有人可见,并时刻记住它们。
标记车辆之后,servlet需要准备显示租车信息页面,客户在该页面检验系统给出的信息,以及他的客户概况的内容。前面我们在图7里省略了描述查找CustomerProfile的序列图部分,但现在我们也需要这个概况以及Customer对象。在第5步servlet(从我们未提及的HttpSession对象那里)获得了customerID,它可能已经放进了车辆调度的主页。在第6步,servlet用这个ID创建了一个Customer对象,将它送给populate(
),用客户ID组装它的实例变量。在第8步和第9步Customer对象按照一个现在已经熟悉的模式从DataAccess对象那里获得一个dbRecord型的内容。现在Customer对象已经为客户实例化和组装起来,第10步servlet告知Customer对象它对应的CustomerProfile。第11步Customer告知profile它是profile的父对象。现在我们已经在这两个对象之间建立了双向联系。
完成了这个处理以后,servlet预备显示RenterInformation页面,这由RequestDispatcher.forward(
)操作完成。第13步,RenterInfo JSP页面进入活动状态。第14-16步,JSP从Customer和CustomerProfile对象获得一般信息,对应的数据将显示出来等待确认。第17步RenterInfo页面显示在客户的浏览器上等待确认完成。第18步客户指出他已经在RenterInfo页面确认、更改或键入数据,并准备继续预约。第19步我再次一般性地指出JSP将与Customer和CustomerProfile对象(记住它们是JavaBeans)交互,将其根据屏幕数据更新。第20步,RenterInfo
页面发送至servlet,指出租车信息已经完备。此后我们将建立模型,描述系统如何提供预订的总括信息,包括提供购买保护产品等等。
我们在这张序列图里已经画出了不少交互,也发现了类中更多的操作。将我们的序列图消息提升至接受类的操作,我们就得到类图的一个新的版本,如图10所示。
图10:由操作更新的第二个版本的类图
我们现在看到对Vehicle、Customer和CustomerProfile加入了新的操作。这就是迭代的用例驱动方法。从用例和类表出发,我们从类里选择需要交互的对象,然后构造一张序列图表示这种交互。分析阶段的消息现在分配给基于职责的类,这些职责是我们赋予给每个类的。之后,消息提升为接受消息对象的类上的操作。在理想情况下,这些操作被确认为必须的,因为生成它们是为了满足用例的需求,而用例是包含了许多系统的功能性需求的。回想一下这个用例设计步骤称作描述设计对象之间的交互。在我们的序列图里显然已经实现了这一点,并且捕捉到了使那些类图中的交互称为可能的静态结构。
用例设计第3步:用子系统简化序列图(可选)
用例设计的下一步骤是用子系统替换公共组以简化重复行为。在我们的例子里,已经确定了一个子系统,DataAccess对象。这个子系统暴露retrieve
...( ) 操作以从数据库获得对象数据。在DataAccess子系统内部实际上已经有了很多东西:SQL语句生成,错误处理,结果集管理,等等。但内部细节对查询数据获取(之后也有数据存储)的客户对象是隐藏的。
通常不太会意识到需要一个子系统(带有简化界面),直至被一大堆复杂的类和界面缠住的时候,我们才终于认识到它们彼此有一个自然的关系(比如打印子系统中的类:Spooler、QueueManager、PrinterDrivers等等)。把类组合成子系统有一条好的原则,Robert
Martin的公共封闭原则:
包(或子系统)中的类应当按照同类的变更封闭在一起。对一个封闭包(或系统)的变更影响到包里所有的类,但不影响其他任何包。
换言之,我们要把具有公共目标的类组合在一个清晰的边界内,以使该包/子系统内部的变更隔离在其内部。当我们通过引入子系统来简化模型时,我们把序列图中大的段落替换成向该子系统发送一条简单的消息。这就把前者的复杂性隔离在子系统内部,我们就可以为子系统的内部交互开发独立的模型,而子系统也就成为整个系统中一个可重用的组件。
用例设计第4步:描述持续相关行为
然而,当我们把DBMS的某些细节正确地藏进DataAccess子系统时,我们实际上犯下了一个重大的错误:我们的分析对象(Vehicle、RentalLocation……)获得了DBMS的信息。分析对象应该只知道业务逻辑,而不该知道存储位置、dbRecord结构布局。这样做为什么是不好的?考虑一个作为对象变更主键的简单变更的影响,回顾我们目前的设计是让分析对象(如RentalLocation)把一个特殊的标识符(如locationID)传给DataAccess对象。如果该标识符用作主键,又有变更,我们只好在RentalLocation对象里改变逻辑。
有没有什么办法能限制或消除这种对分析对象的影响呢?
有。我们可以在软件架构文档中描述的显式物理隔离之外,还达到这种逻辑隔离,其方法是引入三个新的对象:
- 一个factory对象,为我们创建分析对象,完全从DBMS中组装
- 一个DB对象,它是DBMS能意识到的分析对象的孪生对象
- 一个persistence interface(持续界面),这是一个子系统,用于对数据库读写信息。
在图11的序列图中,我们引入了这三个对象,在servlet需要Customer对象从在线存储中恢复时重新创建了交互。
图11:带有持续层的设计序列图
这是一个更具封装性,也更具重用性的设计。对每个必须从DBMS中存取的分析对象(如Customer),我们创建一个"DB-aware"(“数据库可意识的”)对象(如DBCustomer),它能理解它“正常”的孪生的数据字段与格式。这些DB-aware对象会驻留在Web服务器上,由servlet之一创建和使用。DB-aware对象知道从哪里怎样连接,怎样建立一个SQL语句并使之执行,以及怎样获得结果数据并存储到该分析对象中。现在,DBMS的格式或结果集数据结构的变更被隔离在ObjectFactory层之下,分析对象决不会受到影响。
我们还得评估一下存储结构,确信我们可以存取对象需要的数据。表1-7是预约DBMS布局的一个逻辑模型的例子。注意各表中大部分字段匹配为类的属性。但有些表的字段(如表3:车辆:Date
of Purchase(购买日期))不在相应的类中,因为这类字段与预约车辆用例无关。
表1:租借地点(Rental location)
LocationID
<PK> |
Street
Address |
State |
Sales
Region |
|
表2:预约(Reservation)
ReservationID
<PK> |
LocationID
<FK> |
VIN
<FK> |
CustomerID
<FK> |
StartDate
&Time |
ReturnDate
&Time |
Quoted
Cost |
Payment
Method |
|
表3:车辆(Vehicle)
VIN
<PK> |
Manufacturer |
Make |
Model |
Assigned Location
<PK> <FK> |
Vehicle
Category |
Date Of
Purchase |
Current
Mileage |
. . . |
|
表4:保护产品(Protection product)
ProductType
<PK> |
Eligibility
Codes |
Lower
Limit |
Upper
Limit |
Benefit
Increment |
|
表5:客户(Customer)
CustomerID
<PK> <FK> |
Name |
Street
Address |
Email
Address |
Business
Phone |
Other
Phone |
Drivers
License # |
Issuing
State |
License
Exp. Date |
Date Of
Birth |
|
表6:客户概况(Customer profile)
CustomerID
<PK> <FK> |
Default
Vehicle
Category |
Smoking
Preference |
Default Damage
Waiver |
Default Personal
Accident Insur. |
Default Supplemental
Liability |
|
表7:奖励计划(Award program)
AwardID/CustID
<PK> <FK> |
Current
Balance |
YTD Redemptions |
|
此外,注意主键(<PK>)和外来键(<FK>)属性是怎样表达车辆调度类图的关系的。Reservation表从CustomerID<FK>字段知道相应的Customer。RentalLocation表可以通过Vehicle表中的Assigned
Location主键字段获取所有它分配的车辆。DB-aware对象创建的SQL语句根据所访问数据库的物理数据模型而生成。SQL语句"SELECT
* FROM VEHICLE WHERE ASSIGNEDLOCATION = 42355 ORDER
BY VEHICLE.VEHICLECATEGORY"返回的结果集是所有分配给该租车位置的车辆,按车辆类别排序(廉价车、小型车等等)。
现在我们可以理解这条非常重要的表述了:你发现和使用的设计类依赖于你采用的设计方法。在我们解决持续性的第一个方法中,我们把DBMS的信息放在表示业务抽象(即分析类)的对象里。但在第二个方法中我们另外创建了一个DB-aware对象层,专门用来读写DBMS的数据。
用例设计第5步:描述设计机制
在分析车辆租借系统时,我们为以下分析机制确定了其需要:
持续性。在正常的预约会话或者预约变更会话中,Reservation和Vehicle对象会经历数据和/或状态的变更。所以我们需要一个持续机制来存储新的对象数据。在分析阶段我们只需要为持续存储指定一个有意义的名字,而如何实现这个持续性的细节将在后面的设计阶段解决。
安全性。不能把其他人的预约显示给当前的客户,所以必须指定一个机制来确保安全认证。
遗留界面。所有的车辆信息来自公司车辆系统(Corporate Vehicle System),它为所有的租借地点提供集中的车辆信息维护。如何与公司车辆系统对话依赖于我们能选择的设计机制。
在设计阶段,我们重新评估这些东西,并把它们对应到设计机制中,这些设计机制是根据持续性(表8)、安全性(表9)和遗留界面(表10)所需的分析做出的特殊解决方案。对其中每一个我都加上了满足设计机制的实现机制,如表8所示。
表8:
机制 |
持续性 |
设计机制 |
RDBMS——分析确定要以一种持续的方式访问和存储对象信息。因为我们的公司有几个相关的数据库管理系统(RDMBS),我们的设计机制也要指定RDBMS。我们拥有理解这一技术的职员,现有的数据基础设施包括以SQL为界面的工具(如报表生成器)。 |
实现机制 |
JDBC——我们在上一段特别提及了它,来完成Factory类、DB-aware类与Persistence
Interface类。但Persistence Interface究竟如何访问目标RDBMS?在我们的场景里,假设Reservation
RDBMS是运行于Unix服务器上的Oracle 9i系统,但有可能为Windows
2000上运行的SQLServer所取代。在此场景对RDBMS用一个JDBC界面会使我们获得需要的灵活性。 |
|
机制 |
安全性 |
设计机制 |
安全认证——分析确定需要确保客户只能看到他/她自己的预约。我们用客户的Awards
ID号码(这是唯一确定的标识符)作为顾客数据库的索引。 |
实现机制 |
Unique Customer Number (Awards ID number)作为唯一标识符在Customer表上用作RDBMS索引。 |
|
机制 |
遗留界面 |
设计机制 |
这一机制在我们这个简单的车辆租借系统中并不必要。在用例分析中,我们规定车辆信息由公司车辆系统维护,它自己就是一个RDBMS。持续性机制提供的服务使我们能够在我们的RDBMS中插入或提取信息。
如果我们必须与IBM MVS大型机上的CICS应用程序通信,我们需要指定所用的通信协议(例如LU
6.2 APPC)。 |
实现机制 |
无。 |
|
结论
在这两篇系列文章中,我给了很多例子来说明在我们的生产环境的指定技术依赖下,怎样从包含许多系统需求的初始用例出发,达到问题域的分析和方案域的设计。在系统开发中还有更多的问题要考虑,但我相信这些例子已经给出了整个流程的一次实践性的漫游,达到了我的初衷。在我给出的设计级别的序列图和类图细节的基础上,读者应该发现把这些表达翻译成Java类定义或JSP已经是一个简单直接的事情了。通过我们的设计工件,可以直接进入为系统作概念证明编码,或者编写产品代码的阶段。每个用例将遵循文中的流程,直至所有的用例在这一迭代周期中完成,这样我们将会完成系统的大部分编码工作,完成对用例中捕获的所有重要业务功能的编码工作。
参考文献
Rumbaugh et al., Object-Oriented Modeling and Design.
Prentice-Hall, 1991.
Robert C. Martin, Agile Software Development: Principles,
Patterns, and Practices. Prentice-Hall, 2003. An award-winning
book that covers a broad menu of essential design practices.
Eric Gamma, et al., Design Patterns: Elements of Reusable
Object-Oriented Software. Addison-Wesley, 1995. Required
reading for any developer doing serious development
today.
|