转载自:http://blog.csdn.net/FcBayernMunchen/archive/2010/08/15/5813667.aspx
这是我在csdn博客的第2篇技术文章,本来按原计划是要介绍开源ajax框架buffalo的第2部分,即js<>java的序列化,这里面涉及不少设计模式的运用和JAVA SE知识,代码精简,比较精彩。但是由于个人时间有限,在抉择之后,打算先写一篇关于面向对象分析的文章,也算是对自己过去1年多在这方面学习的总结。我选了比较简单且大家也比较熟悉的案例来分析,案例虽然简单,但是基本的分析方法和推导过程还是一致的,我主要想讲的是原始需求是怎么通过层层分析和推导而形成最后可执行代码的,限于自己的个人能力,如果有谬论和错误之处,还望同行多指教和帮助,共同进步。
原始需求描述如下:某公司鉴于业务和员工的快速发展,为了提升整体工作效率,公司准备开发一套员工报账系统,取代原来的人工处理方式,更加方便的服务于员工日常的账务操作。财务部门能够通过账务系统定期向各部门负责人反映账务统计情况,并设置和维护相关额度准则。系统应该具有基于先进技术的操作界面。
这段描述里包含的业务目标大致有二:
1. 为员工提供账务的自动化办理,提高办事效率,方便员工。
2. 方便财务部门管理好账务信息。
这些业务目标一般在项目的招标书里都有相关的描述,也可以由开发方整理得出。之所以这里要把业务目标列出来,是因为我所采取的方法里,业务目标是进行需求分析的第一步,接下来的推导过程和业务模型的建立都是根据业务目标开始的。
整理出了业务目标后,接下来先不要一头扎进具体的业务流程和业务细节之中去,应该先把涉众找出来,整理出一份涉众分析报告,涉众就是和这个项目相关的人。也不要就去考虑技术实现细节,要用什么先进的技术,界面如何美观,性能如何优越等等,虽然这些确实重要,但是相比起来,忠实的实现涉众的期望,满足涉众的需求才是最为重要,也是一个项目成败的关键。在实际的项目中,我们可以通过需求调研找出相关的涉众,这里我就直接列出本案例的涉众分析报告:
员工:公司的正式录用雇员; 期望:通过网上办理账务业务申请,计算机控制流程。
部门经理:部门负责人,负责审核员工提交的申请;期望:方便审核操作,通过计算机代替原来的手工审核方式。
公司主任:公司负责人,负责2次审核员工提交的申请;期望:方便审核操作,通过计算机代替原来的手工审核方式,界面友好易用。
财务主任:公司财务部门负责人,负责发放报账款项; 期望:通过计算机转账的方式替代原来的人为付款方式。
以上的涉众分析报告是很简单的了,在实际稍微复杂些的项目中要下功夫好好整理清楚一份完整的文档才是,因为接下来的业务用例获取工作也是在此基础上展开的。
这里先罗嗦下业务用例和平时开发中的我们开发人员从项目经理或者需求人员手中拿到的需求文档中的用例什么区别。按我个人理解来说后者是系统用例,两者的关键区别在于抽象层次和用例粒度的不同,系统用例是以人与计算机的每次交互为单位的,而业务用例则是在较高的层次上用于确立业务需求范围和描述系统功能性需求的。也就是说我们在描述业务用例的时候,可以不用去考虑具体和计算机相关的实现步骤和细节,从而降低我们人脑需要考虑的复杂度,专注于确立业务需求范围,抽象就是面向对象的优势所在,不用像过程化思维那样通盘考虑,因为人脑能接受的信息量是有限的。系统用例一般是从业务用例中推导出来的,本文之后会有关于这方面的推导过程。
不知道有没跑题,罗嗦了一段,现在回来,分析下本案例中的业务用例获取工作。说到用例,就必须结合边界和业务主角,否则用例的粒度就会出现问题,因为用例是以参与者(业务主角)为核心的,是由业务主角发起的以达到业务主角完整目标为标准的。要获取用例就必须先得出边界,边界有了,那么边界外的业务主角就有了,那么业务主角对这个边界内的目标就是用例了。用 UML 表示如下:
我们先来看看一个小例子,没有引入边界的概念对获取用例有什么影响,比如我去食堂就餐,要先领取餐具,然后点菜,打菜的阿姨帮忙盛菜,接着我刷卡付款,去盛饭和汤,之后是找座位,最后才开始就餐。那么领取餐具,点菜,刷卡付款之类的算是一个用例吗 ?说算也算,说不算也不算。因为这要根据边界来确定的,我们都知道用例是以主角发起的以完成主角的完整目标为标准的。这里的主角就是我本人,要确定我的目标就必须先确定边界,比如以整个食堂为边界,那么我去食堂的目的就是就餐,就餐才是我的完整目标,而其他诸如领取餐具,点菜,刷卡付款之类的都不是我去食堂的目的,这些只是我完成就餐的步骤而已,但如果把边界粒度降低到食堂的内部,那么这个时候领取餐具,刷卡付款之类的也是一个用例了,虽然都是用例,但是和就餐这个用例的粒度是不同的,因为他们边界所在的抽象层次不同。所以要描述用例就必须先划分出边界来,主角站在边界外对这个边界提出目标,一个目标就是一个用例,否则在描述系统的时候就会出现如我去食堂的目的是刷卡付款这样的笑话来,当然了,除非我去食堂的目的真的只是为了付款。
回到本文的案例中来,开始进行获取业务用例的分析,刚才说了,要获取用例必须先确定好边界,那么怎么确定边界呢 ? 这个时候我们前面划分业务目标的作用就体现出来了,我们可以以每个业务目标为一个边界,因为所有业务目标汇集起来就表示达到了系统建设目标,而针对每个业务目标定义的边界,明确了哪些涉众与这一业务目标有关,他们作为业务主角站在这一边界外提出他们的期望,这些期望作为用例都是为实现这一业务目标服务的,获取业务用例的方向就明确了(不符合这一业务目标的期望则不被采纳)。
如上图,边界和业务主角都已经有了,接着就是找出用例了,我以员工账务服务边界为例,根据涉众分析报告和客户访谈(这个在实际项目中需要好好历练的,我觉得要有技巧引导客户,还要有较强的总结概括能力吧)得出的。假定我从与客户访谈的结果中得出员工对这个系统的期望和目标有通过计算机申请报销业务,申请借款业务,这两个期望都是与员工账务服务这个特定的业务目标有关的,所以可以作为业务用例被纳入到员工账务服务边界之中。如果假设员工也可以参与管理账务信息,那么得出的员工对系统的期望就不止这两个,但是分析的时候要注意与员工账务服务这一业务目标相关的期望只有申请报销业务和申请借款业务两个,其他的期望是与管理账务信息这个业务目标有关,应当被划分到管理账务信息边界中去。
有的人可能会问了,貌似部门经理也有对员工账务服务边界有贡献啊,不是有参与审核吗,为啥部门经理审核账单就不能算一个业务用例呢?之所以会出现这个疑惑和误区还是因为没有分清楚边界造成的。因为对于员工账务服务边界来说,处于该边界的之外的业务主角只有员工,而部门经理,公司主任,财务主任都是在这个边界之内的,他们的工作都只是完成业务主角提出的业务用例的一个步骤,在这里他们作为业务工人无权提出业务用例,他们的职责可以在绘制用例场景活动图的时候通过泳道体现出来。
接下来是建立业务模型阶段,建立业务模型的目的是为了通过UML这种对象语言将现实世界描述出来,是我们为了理解客户的业务并和客户达成业务上的理解而建立的模型(我们的系统将要面对的问题领域就是这个样子),它不需要考虑计算机环境,相对于系统模型来说,他没有加入计算机元素,是对现实业务的一种直观的理解。我们平时开发时接触的《软件需求规格说明书》来源于系统模型,他描述的是软件系统要实现的功能范围,和计算机环境密切相关,软件需求只是整个需求过程的一部分,可以从业务需求中推导出来的。
业务模型主要包括业务用例,业务用例实现场景,业务规则,业务用例规约等等,限于个人掌握程度及个人精力所限,本案例中我主要讲述业务用例和业务用例场景图,业务用例场景主要是描述业务用例的执行过程,一般通过活动图中的泳道来绘制,这里以“申请报销”用例来说明:
(报销申请的业务用例场景活动图)
其他用例的场景图也是依样画葫芦了,再搭配上业务用例规约的文字描述(用例前置条件,后置条件,流程等等),这个报销申请用例的描述也就基本形成了,所有的业务用例如此之后形成业务模型,然后以业务模型为基础,撰写用户业务需求说明书。
接下来要做的就是引入计算机,降低用例粒度,进入系统模型的建立过程。同样这里也是包括系统用例和系统用例场景,系统用例可以从业务用例场景中推导出来,业务用例场景一般描述为某某做什么,某某做什么,这个某某做什么就是一个备选的系统用例,然后从备选用例中确定系统用例,分析过程如下:
员工申请报销,这是一个填写报账单的过程,是通过计算机完成的,可以直接映射成一个系统用例;
部门经理审核报账单,这是通过计算机来操作决定是否通过审核,可以直接映射成一个系统用例;
部门经理说明(填写)拒绝原因,经过分析,这个备选用例其实是审核报账单的结果之一,也就是说审核报账单中包含了说明拒绝原因这个行为,所以取消部门经理说明(填写)拒绝原因的独立用例资格,将它作为部门经理审核报账单的包含用例。
公司主任审核报账单,公司主任说明(填写)拒绝原因 同上。
财务主任发放还款,这个备选用例是否能成为系统用例要看情况的,如果财务主任是人为的发放现金或者人为的去银行汇款转账,那么没有通过计算机(意思是该系统)进行操作,就不能算是一个系统用例;而如果财务主任是通过系统提供的转账功能汇款的话,那么就是一个系统用例。回顾涉众分析报告后我们确定这可以成为一个系统用例。
接下来我们绘制系统用例场景,看看他们是如何通过人机交互,通过计算机来实现的,以员工申请报销为例子,通过泳道绘制用例场景图:
其他系统用例的场景图绘制也是依样画葫芦了,这里就省略了。所有系统用例和系统用例场景图绘制出来后,再配合相应的用例规则,用例规约(前置条件,后置条件,流程等),那么完整的系统用例模型就出来了,以此为基础便可以撰写系统需求文档,即软件需求规格说明书。
到此为止,用例已经全部找出来了,接着就是要进入用例实现阶段了,因为用例只是描述了系统应该做什么,是对系统提出的设想,用例实现的目的就是实现需求,把设想变为现实,由于我们采用的是面向对象的方法,所以用例实现的过程就是用对象之间的交互来实现需求的过程。
不少人到这一步,包括我自己,可能直接进入类设计,数据库表结构设计了,但是经常说不清楚类是如何推导出来的,为什么是设计2个类,为什么不是3个类 ? 美其名曰:经验,哈哈,无非就是拍脑袋拍出来的咯,尤其是在业务复杂的大型项目中,这种拍脑袋出来的设计估计要经过反复修改才能满足需求。现在我发现,原来从系统需求到设计之间可以通过分析模型作为过渡,通过分析模型推导出设计模型,推导出设计类。分型模型就是采用分析类(边界类,控制类,实体类)来实现用例场景的一种对象模型,这个抽象层次上需求已经通过对象之间的交互实现出来了,而又不必去关注具体的技术细节,如采用什么语言,什么框架之类的,可能安心的为需求到设计之间的跨越做一个桥梁。绘制分析类图一般需求根据用例场景来推导,先一步步的分析场景中的活动:
创建新申请报账单:这是一条由外面发出的命令,需要用边界对象接受它;
展现录入新报账单界面:这是一个控制逻辑,需要有控制对象处理;
输入报账单信息:这是一个人工活动,由边界接受,报账单是一个实体对象;
提交申请:这是一条外界发出的指令,由边界对象接受;
验证信息:这是业务规则,通过控制对象来处理;
保存申请单:这是一段处理逻辑,由控制对象处理,同时,报账单作为实体对象封装了要处理的数据;
发送邮件通知:这是一段处理逻辑,需要由控制对象处理;
显示结果:这个是处理结果,用控制对象处理,并反映到边界对象。
根据上面的分析,接下来我绘制出员工报销申请用例实现的分析类时序图:
在绘制该时序图的过程中我们得到了关键对象以及这些对象的方法,接下来把这些对象及其方法绘制在一个图里,定义出他们的关系,就得出了分析类静态图:
(这个图其实有点小问题,就是这个矩形元素代表的是设计类而不是分析类,分析类的形状应该是绘制时序图那时候的圆形,也没有 void这个语言层次的东西,我用的建模工具是 EA ,不知道的是工具不支持在分析类中绘制方法,还是我自己没找到。反正 rose 中是可以的)
用分析类对象实现用例场景的过程就是类的推导过程,现在我们已经得到初试的类及其方法,虽然看上去还很粗糙,但已经脱离了需求视角,进入系统设计的视角了。
这些分析类就是我们进行系统设计的基础了,分析类结合采用的具体框架(比如SSH),架构等,就可以推导出设计类,产生设计模型了。
推导设计类的过程由于要结合具体框架,可能要实现某某接口,继承某个抽象类等原因,这里就不说了,等过段时间我再新写一篇文章来说吧。由于我工作中的项目采用 SSH框架,所以我曾经疑惑觉得怎么没有看到 Action 类啊,Service类呢, pojo呢,DAO呢,没看到啊!后来才恍然醒悟,哦,原来所处的抽象层次不同,分析模型的抽象层次比设计模型高,不涉及到具体的框架,架构,语言等实现方式,所以在这个抽象层次上,可以不去考虑实现细节,屏蔽掉无关的信息,而专注于通过分析类的3种对象之间的交互来实现需求,为需求到设计之间搭建桥梁,设计模型就是在分析模型的基础上结合具体框架,架构,语言等实现方式实例化分析模型的过程。完整而全面的分析模型就可以作为系统概要设计文档了。
其实我个人觉得,从业务模型到设计模型(中间可能还有概念模型),到分析模型,再到设计模型,这种建模的过程就是一个降低抽象层次和边界粒度的过程,类似于我们要描述一个东西,比如汽车,我们可以这样说:站在汽车这个抽象层次上,我们看到的是车身,轮胎等边界;降低抽象层次到车身上,我们面对的有方向盘,发动机,座位等边界;站在发动机这个抽象层次上,我们看到的是引擎,活塞等边界。。。。。。。。这个抽象层次可以一直延伸下去,采用这种自顶向下的方法把一个事物描述清楚。抽象层次的好处是无论站在哪个层次上都只需要面对有限的复杂度和结构,从而帮助我们理解清楚这个层次上的对象是如何工作的。边界和抽象层次是相伴的,不同的抽象层次面对的边界粒度就不同。编程中所谓的“针对接口编程”,其实也就是把对对象的认识附加在接口这个边界上,我们只要清楚他能做什么就可以,不需要降低边界粒度去考虑具体实现方式,这样才带来了具体实现可替换的灵活性。
这篇文章就写到这里吧,感觉要再往下写,我目前也是有心无力了,毕竟以我自己目前的能力能写出这么多来也是不容易了,虽然本问中的案例非常简单,但是这种面向对象的分析思路都是一样的。
福州这两天天气好热,我把周末两天都拿来写这篇文章了,都是窝在宿舍里,边吹空调边写。其实一个项目要面对的问题远不止本文所讲,比如要针对某个问题建立领域模型,例如权限系统等,本文也省略了业务规则的分析,尽管如此,我觉得已经基本把自己想要说的表达出来了,以我目前的水平,能说这么多也算有个交代了,至少我觉得自己已经找到了通往面向对象分析的大门了。路漫漫其修远兮啊。。。。。
最后引用面向对象大师Grady Booch在2004年IBM Developer Works Live!大会的访谈中讲过的一段流传甚广的话作为本文的结尾吧:“我对面向对象编程的目标从来就不是复用。相反,对我来说,对象提供了一种处理复杂性问题的方式。这个问题可以追溯到亚里士多德:您把这个世界视为过程还是对象?在面向对象兴起运动之前,编程以过程为中心,例如结构化设计方法。然而,系统已经到达了超越其处理能力的复杂性极点。有了对象,我们能够通过提升抽象级别来构建更大的、更复杂的系统——我认为,这才是面向对象编程运动的真正胜利”。
转载自:http://blog.csdn.net/FcBayernMunchen/archive/2010/08/15/5813667.aspx