构建面向对象的应用软件系统框架 第15章
第15章 简述
面向对象的软件工程,同传统的面向过程的软件工程相比,在需求的获取、系统分析、设计和实现方面都有着很大的区别。UML是OOA和OOD的常用工具。使用UML来构建软件的面向对象的软件工程的过程,就是一个对系统进行不断精化的建模的过程。这些模型包括用例模型、分析模型、设计模型,然后,我们需要使用具体的计算机语言来建立系统的实现模型。当然,在整个软件工程中,我们还需要建立系统的测试模型,以保证软件产品的质量。
使用面向对象的工具来构建系统,就应该使用面向对象的软件工程方法。然我,我们经常会发现,在实际的开发过程中,很多开发人员虽然能够理解UML的所有图形,却仍然不能得心应手的使用UML来构建整个项目,其很大的原因,是仍然在使用原有的软件工程方法,而不清楚如何使用UML来建立系统的这些模型,不清楚分析和设计的区别,以及他们之间的转化。
应用软件系统,就其本质来说,是使用计算机对现实世界进行的数字化模拟。应用软件的制造过程,按照UML的方法,就是建立这一些列模型的过程。本文将就一个图书馆系统,说明如何使用UML来对系统进行这一系列的建模。
关于这个图书馆系统,基本的需求比较简单,就是允许学生可以在图书馆借阅和归还图书,另外,也可以通过网络或者图书馆的终端来查阅和预订书。当然,图书馆管理员也可以对图书进行管理。为了简化系统,我们没有把图书馆中的人员作细分。
之所以采用这个相对简单案例,是因为很多人都对图书馆系统有很强的感性认识,这样,读者不需要花很多的时间来理解系统包含的业务知识。同时,也因为本文只是对使用UML的过程做一个探讨,着眼于使用UML进行建模的过程,说明各个层次的模型之间的区别和联系,展示系统演进的过程,而不会深入UML的细节方面。对于更加复杂的系统,其分析和设计的方法是相通的,可以举一反三。
第16章 用例模型——系统需求的获取
用例模型定义系统做什么,是用来获取系统需求的有效手段。用例模型由“角色”和“用例”组成。我们在构建一个用例的时候,通常要做的第一件事情是识别角色,或者说,参与者。然后我们我们需要识别系统为参与者提供的服务,或者说,参与者的行为,也就是用例。最后,我们确定角色和用例之间的关系。在这个图书馆系统中,我们可以识别出的角色有学生和图书管理员。整个用例模型包含的用例有:借书、还书、查阅图书、预订图书,以及图书维护。用例模型可以用用例图表示如下:
确定有效用例的关键是,检查用例是否包含了一个完整的功能。用例不能定的过细,不能把一个完整的功能的一个部分作为一个用例,也不能在一个用例中包含过多的功能。例如,用户的登录。学生在预定图书的时候,可能会需要首先登录系统,这是系统的一个功能。但是,这个功能只是预定图书这个完整的功能中的一个步骤,或者说一个子功能,就不适于做成一个用例。另一方面,借书和还书,都是相对完整的功能,如果把这两个用例合并成一个类似于“处理图书”的用例,显然是不能明确的表达用例需要表达的含义的。
描述用例
需要了解的是,使用UML进行系统建模,并非只是意味着画UML图形,对UML图的文档说明是同样重要的。学习UML,不仅仅要学习UML图形,相应的文档描述方法也同样要学习,也同样重要。
在描述用例时,我们可以用文字来描述,也可以用其他图形来描述,例如,顺序图或者活动图等等。下面给出了一个RUP中推荐的描述用例的完整的结构:
w 名称。名称无疑应该表明用户的意图或用例的用途,如“研究班招生”。 w 标识符 [可选]。唯一标识符,如 "UC1701",在项目的其他元素(如类模型)中可用它来引用这个用例。 w 说明。概述用例的几句话。 w 参与者 [可选]。与此用例相关的参与者列表。尽管这则信息包含在用例本身中,但在没有用例图时,它有助于增加对该用例的理解。 w 状态 [可选]。指示用例的状态,通常为以下几种之一:进行中、等待审查、通过审查或未通过审查。 w 频率。参与者访问此用例的频率。这是一个自由式问题,如用户每次录访问一次或每月一次。 w 前置条件。一个条件列表,如果其中包含条件,则这些条件必须在访问用例之前得到满足。 w 后置条件。一个条件列表,如果其中包含条件,则这些条件将在用例成功完成以后得到满足。 w 被扩展的用例 [可选]。此用例所扩展的用例(如果存在)。扩展关联是一种广义关系,其中扩展用例接续基用例的行为。这是通过扩展用例向基用例的操作序列中插入附加的操作序列来实现的。这总是使用带有 <<extend>> 的用例关联来建模的。 w 被包含的用例 [可选]。此用例所包含用例的列表。包含关联是一种广义关系,它表明对处于另一个用例之中的用例所描述的行为的包含关系。这总是使用带有 <<include>> 的用例关联来建模的。也称为使用或具有 (has-a) 关系。 w 假设 [可选]。对编写此用例时所创建的域的任何重要假设。您应该在一定的时候检验这些假设,或者将它们变为决策的一部分,或者将它们添加到操作的基本流程或可选流程中。 w 基本操作流程。参与者在用例中所遵循的主逻辑路径。因为它描述了当各项工作都正常进行时用例的工作方式,所以通常称其为适当路径 (happy path) 或主路径 (main path) 。 w 可选操作流程。用例中很少使用的逻辑路径,那些在变更工作方式、出现异常或发生错误的情况下所遵循的路径。 w 修改历史记录 [可选]。关于用例的修改时间、修改原因和修改人的详细信息。 w 问题 [可选]。如果存在,则为与此用例的开发相关的问题或操作项目的列表。 w 决策。关键决策的列表,这些决策通常由您的 SME 作出,并属于用例的内容。将这些决策记录下来对于维护团体记忆库 (group memory) 是相当重要的。 |
下面,我们对借书这个用例来做描述:
名称:借书”。
说明:学生在图书馆挑选好需要的图书后,通过图书管理员把书借回去。
参与者:学生,图书管理员
频率:每天可能会有很多次。最繁忙的情况是,借书的人非常多,按照现在的速度,大约每分钟完成一个人的结束工作。
前置条件:无
后置条件:修改所借出的图书的剩余数量。
假设:借书者总是从图书馆找到书,然后才能拿书办理借书手续,因此,总是有足够的书可以出借。
基本操作流程:借书成功。
1) 学生将所借图书和借书证交给图书管理员
2) 图书管理员将学生借书证号码和所借图书输入系统
3) 系统校对借书信息,比对该学生以往借书情况和当前借书情况,如果不存在不允许借书的情况,则记录借书交易的信息,并且修改相应的馆藏图书的数量信息。
4) 如果该学生已经预订了这本图书,则撤销该预定。
5) 报告交易成功。
可选操作流程:所借图书超出最大借书数量。
1) 学生将所借图书和借书证交给图书管理员
2) 图书管理员将学生借书证号码和所借图书输入系统
3) 系统校对借书信息,比对该学生以往借书情况和当前借书情况,发现已超出最大借书数量,则停止当前交易,并且提示用户错误原因。
4) 图书管理员可以应学生的意见,减少借书数量,并重新提交系统。
流程活动图:见图一。
图一:借书活动图
问题:暂无。
决策:略。
上面,我们就这个用例做了一个比较详细的描述。按部就班,我们就可以逐步把整个系统的用例模型完成。
再次强调一点,使用UML建模,文档说明和图形同样重要,并且,要使用UML的方法来编写文档。
第17章 分析模型——开发者的视野
在系统分析的过程中,我们所关注的依然是问题,但是,同用例模型不同的是,用例模型是从最终用户的角度来看待问题,而分析模型是从开发者的角度来描述问题。用例模型的主要工作是描述现实世界的业务流程,而很少会涉及系统的概念。分析,则是从系统的角度来来看待软件应该为用户提供的服务。同样,同设计不同的是,分析仍然停留在“做什么”的层次,。而设计,则需要解决“怎么做的问题”。
开发语言等技术选择通常不会在分析模型中考虑。分析模型是独立于实现的,这样,可以提供最大的复用,并且,可以帮助开发人员方知过早的陷入技术的细节中去,从而能够从一个更加一般的角度去理清思路。
静态模型的建立
进行分析建模的第一步,通常是识别对象,然后提取出类。考虑著名的MVC模式,我们需要识别实体、控制和边界三种对象。按照MVC模式来为识别对象做指导,是非常好的做法。对象识别的结果,就是我们所需要的静态模型,通常表现为类图。
我们首先识别出实体对象,这些对象通常来说是比较明显的,例如系统中的角色,系统需要处理的资料,如本系统中需要处理的图书资料等;有些实体对象需要稍微分析一下才能得到,例如,在本系统中,为了记录图书借还的信息,我们可能需要一个对象来专门记录这一信息。这些对象就是所谓的Modal(实体类)。
然后我们需要识别为了完成系统业务逻辑而需要的业务逻辑对象,以及同用户进行交互的界面类,在MVC模式中,他们分别对应于Control(控制类)和View(边界类)。在分析阶段,这些对象通常都按照比较自然的方式来组织,例如,为了完成一个业务功能,我们通常需要一个控制类和一个边界类,控制类执行业务逻辑,边界类同客户进行交互。当然,这不是绝对的,在进行进一步深入的分析后,这些类可能会被分解和合并。
一口不能把所有的饭都吃掉,系统分析也是这样。我们需要一个一个用例的来进行分析。现在,我们首先来分析借书这个用例。
在这个用例中,我们首先可以识别出一些直接的对象,包括图书管理员(BookAdmin)、学生(Student)、图书(Book),然后,稍作分析,我们会发现我们需要一个实体对象来记录图书的借还信息(BorrowInfo)。最后,我们发现,在借书的过程中,我们会使用到预定图书的信息(SubscribrInfo)。到这一步,我们基本完成了实体对象的识别。然后,我们发现我们需要一个借书的控制类(Borrow)来执行借书的动作,以及一个用户界面(BorrowInterface)来接受用户的输入。这样,初步的模型我们就可以建立了。
图二:借书类图
在分析模型中,我们也需要识别出类的一些属性和方法。同样的,为了避免过早的陷入细节中,以及适应将来在设计时类的变化,在分析模型中,我们一般只把一些主要的属性和方法标识出来。例如,对于Student类,我们只需要Name和CardNo(借书证号)属性,对于Book,我们只需要BooKID、AllCount(书的总数)、CurrentCount(当前数量)等属性。现在,我们为我们的类图添加上述属性,就可以得到下面的结果:
图三:借书类图
不过,把这些属性都标上后,图形就比较难布置。因为文章版面的问题,在以后的文字中,除非必要,一般会把属性都隐藏起来,这样,看起来会整洁一些。
依样画葫芦,我们把还书、查阅图书、预定图书、管理图书这些用例一并分析后,我们就能够得到整个系统的静态分析模型。
当然,我们随后需要对这个初步的模型作进一步的整理和分析,类图会发生变化,各个类之间会产生一些联系。我们也可能会使用一些分析模式,来进一步优化我们的分析结果。关于分析模型的知识,可以参见Martin Fowler所著的《Analysis Patterns——Reusable Object Models》一书。在这里,我们就不进一步做探讨了。
动态模型的建立
在面向对象的系统中,业务流程表现为对象之间的交互。我们有了上面分析的得到的对象后,就可以来描述他们是怎么进行交互和协作的了。在UML中,我们可以使用顺序图、活动图或者状态图来建模这些动态的过程。同样的,我们首先来看借书这个用例。
在借书这个用例中,有两个事件流:借书成功(正常事件流)和所借图书超出最大借书数量(非正常事件流)。我们首先来做“借书成功”这个事件流,下面是这个事件流的顺序图:
分析过程中,消息的传递同后面的设计模型和实现模型相比,可能会有区别,也不太严密,例如,Borrow的过程中,记录BorrowInfo的动作,到最后,可能不会是BorrowInfo这个实体类自己来记录(往往会有一个实体访问类来完成这个功能,如JDO中的PersistanceManager,这取决于你采用的具体的技术框架),但是,在这里,我们可以这样来传递消息,表示需要记录这个信息。
同样的,我们也可以为“所借图书超出最大借书数量”来作他的顺序图,这个图相对简单一些:
同样的,我们也可以为其他用例的其他事件流创建动态模型,在这里就不一一画出来了。
动态模型和静态模型的建立是一个交互的过程。在建立动态模型的过程中,我们会发现一些新的类,也会为已有的类找到一些新的属性和方法,这样,我们会需要去修改我们的类图。反之亦然。
当分析模型完成后,我们就对系统需要完成的功能有了一个比较完整和清晰的认识,下面,就可以开始我们的设计工作了。
第18章 系统设计——实现方案
设计是对系统的详细描述。我们需要在这里提供详细的解决方案。设计同分析所使用的工具一样,也需要建立静态和动态的模型,也同样使用类图、顺序图、协作图、活动图、状态图等来表示。
在正式进行设计之前,我们还有一些工作需要完成,那就是选择我们的技术方案,这会影响到我们设计。
技术选择——设计前的工作
设计是为实现服务的,实现时准备采用的技术会影响设计方案的采用。以下这些技术问题是需要考虑的:
u 准备使用什么样的客户端?
u 准备采用什么编程语言?
u 准备采用什么框架技术?
u 如果是分布式系统,那么,准备采用什么通信机制?
在这个图书馆系统中,我们发现,对于借书和还书来说,总是在图书馆内部发生,并且客户端的数量是有限的数个,其使用的频率比较高,效率和使用的方便性是需要注重考虑的,而客户端软件的维护工作量相对比较少,可以不用考虑太多,因此我们准备采用传统的Windows Form的客户端。但是,对于图书的查阅以及预定来说,我们希望在整个校园网内提供这个功能,使得学生无论在什么地方都能够使用这个功能,所以,我们会考虑采用Web浏览器的客户端,这样会方便系统的部署。也就是说,我们的系统需要同时支持两种不的客户,显然,采用N层系统结构,把系统逻辑集中在应用服务器上是一个比较好的方案。最后,为了系统的安全,我们希望把Web服务器和应用服务器分开。这样,我们的系统的架构的拓扑图就基本上如下所示:
这是一个典型的分布式系统,在考虑了各种平台和技术之后,我们决定采用EJB技术来构建这个系统,他已经为我们提供了构建应用系统所需要的优秀的技术框架,同时,我希望在客户端和应用服务器的调用中,采用Web Service的方式(试验一下新技术:))。Windows Form的客户端,我们使用Java来创建一个Windows应用程序,Web客户,则采用JSP技术。
当然,你也可以采用微软的.Net平台以及C#语言来完成这个工作,但是由于微软在.Net平台上尚未提供象J2EE,或者JDO一样成熟的应用系统框架,因此,你必须自己来设计这个框架。在这个方面,鄙人也设计了一个自己的Websharp框架,或许可以帮助你省略这方面的工作。关于Websharp的内容,可以参见拙作《面向对象的应用服务层设计》。
这样,我们在具体技术方面的决策基本上就完成了,可以开始进行具体设计了。当然,在实际项目中,可能还有很多细节的工作需要去做,例如系统的约定,设计规范等,但这不是本文讨论的内容。
设计包或者子系统
首先我们需要来对系统进行一个划分。因为我们的系统是一个N层的分布式系统,包含了应用服务器和客户端,而客户端又包含了Windows Form客户端和Web客户端,所以,我们首先把系统分成3个包:应用服务器、Winform客户和Web客户:
因为我们的系统包含了5个用例,每个用例都是一个完整的子系统,因此,我们可以在上述3个包的下面,又各自划分成5个子包。但是,在这里,因为系统比较简单,每个用例需要完成的功能都比较简单,因此,在这里我们就不错进一步的划分了。
设计应用服务器
下面,我们首先来设计应用服务器部分。我们还是从借书这个用例开始设计。
从分析模型中,我们知道,需要一个控制类来完成借书的业务逻辑,在这里,我们设计一个BorrowLogic类来完成这个功能,这个类被设计成SessionBean;同时,因为我们需要向客户端提供服务,而我们又不希望直接把业务逻辑暴露给客户端,所以,设计BorrowServer这个Web Service来向客户端提供服务。这样,实际上,在分析模型中的Borrow类,在这里,被映射成了BorrowLogic和BorrowServer两个类。
同样的,我们需要Book类来记录图书的信息,Student来标识学生,我们也需要BorrowInfo和SubscribeInfo类来分别记录借书和图书预定的情况。这样,建立设计模型中的静态模型所需要的类基本上已经齐全了。当然,到最后阶段,我们需要为这些类补充完整属性和方法。这些实体类,最后都被设计成EntityBean。
这个部分的整个设计模型看上去就基本上会是这个样子:
然后,我们来为借书成功这个事件流设计他的动态模型。我们还是使用顺序图来表示之。
设计客户端
现在,我们为借书成功这个事件流来设计客户端。客户端在这里比较简单,他需要一个界面来接受输入,然后,通过一个BorrowServer的客户端把借书的请求发送给BorrowServer,我们把这个类设计成BorrowClient。类图如下所示:
其Sequent图可以表示如下:
这样,对于借书这个用例,我们就完成了他的设计。依葫芦画瓢,我们就能够完成其他用例的设计。当然,在设计的过程中,我们可能采用一些技巧,来使得我们的设计更加有弹性,更加合理,这是对系统设计的优化。关于这个内容,最好的书籍莫过于著名的“四人帮”所著的《设计模式》一书了。
设计的最后工作,是根据我们设计的对象模型,把数据库的工作完成,并且,给出对象同数据库的映射关系。这个,就不是本文所准备讨论的问题了。另外,从某种意义上来说,数据库的设计,实际上是实现模型所需要完成的工作,他是实现我们的设计的一个部分的工作。这个观念,同原来的设计方法中,设计工作的主要任务就是做数据库设计的观念,不能不说是一个挑战。
可以看出,对于同样一个用例,在分析模型中的一个模型,在设计模型中被拆分成了两个部分。这时因为,在分析中,我们关注的还是系统的逻辑问题,而在设计中,我们必须给出实现这些逻辑的解决方案,包括系统的架构、系统的部署、应用的分布等。在分析模型中的一个类,在设计模型中可能会被映射成两个类,当然,也可能在分析中的多各类,会在设计中被映射成一个类。对于分析和设计的这些不同的侧重点和区别,是我们需要注意的。
实现模型——构造我们的系统
对于实现模型来说,其工作是非常清晰的,就是设计模型,转换成能够运行程序代码,这个工作,是我们所有的程序员每天都在做的事情,因此本文就不准备做过多的讨论了。提醒的一点是,同分析模型到设计模型的转换一样,在从设计模型向实现模型转换的时候,也会发生一些变化,这是正常的事情。
结束语
使用UML来为系统建模实际上是一个非常自然的过程,只要我们按照既定的合理步骤,采用合理的方法,一步一步的进行深入的分析,就能够很好的完成我们的任务。在本文中,我们通过一个简单的系统,演示了这样一个基本的步骤和方法,展示了各个模型的不同点和相互之间的联系,以及他们的转换和演化过程,希望能够对大家在设计更加复杂的系统时有所启发和帮助。