1.前言
最近经常听到一些对于UML质疑的声音。而我也相信发出这些质疑声音的人都是亲自使用过UML的人。而这几年在项目中遇到的能够使用UML作为分析和设计工具的人越来越少。初学UML确实让人感觉无所适从,而UML作为分析设计的工具又确实不想c/c++,java,.dot net等编成语言那样直观,易于应用。子曰:学而时习之,不亦悦乎。学习那些开发语言可以即时看到效果。学UML容易,而想找一个合适的对象来练习就非常困难了。很多人学了UML却一直用不起来,最后他们得出了UML没有用的结论。本文是写给那些想用UML做分析设计的初学者的,以免在UML学习之路上绕太多弯路。
UML是统一建模语言。UML的作品就是模型。项目中不同阶段也需要不同模型来对应。分析阶段有分析阶段的模型。设计阶段有设计阶段的模型。两个模型相互独立,他们之间也有关联。比如:奥运会体育馆设计首先是选择整体方案。这个时候我们看到的是鸟巢的模型。而真正施工的时候,当然需要一个更详细的方案告诉施工单位那里用什么样的钢结构,跑道用什么样的塑胶……软件开发也是这样。UML在需求分析阶段侧重于对需求功能的描述,是描述整个功能。而设计阶段是将这些功能分解,告诉程序员如何开发。这样分析和设计的两个模型就各有不同,有相互关联。他们之间的关系被称为精化。
在开始建立模型之前,除了这些基础知识之外,我们还要选择一个辅助开发的的工具。我选择使用的是Rational公司的Rose。虽然Rational公司被IBM并购,但是IBM后来推出的建模工具并没有超越古老Rational Rose 2003——虽然新工具号称支持UML2.x。其他工具可以替代,但是个人用起来又诸多不方便的地方。所以还是用Rose。
2.需求说明
我们的原始需求非常简单,快速的从文件中读取数据。因为最近项目中有一个需求是处理程序的日志。日志量很大,而我们需要在尽量短的时间内把这些日志处理完。所以就需要一个快速读取日志的工具。
当然,从文件读取数据的速度是由硬盘的IO决定的。作为软件开发者并没有办法让硬盘的磁头转的更快。虽然如此,我们确实有办法让文件读取看起来很快的方法——那就是缓存,把一部分数据提前读取出来,当应用需要处理的时候,从缓存里把数据给应用;而再应用处理数据的时候,我们在后台读取更新的数据填充在缓存中。
总结一下我们的需求,就是做一个双缓冲的数据读取程序。
3.需求分析
使用UML做需求分析也就是对需求建模的过程。UML中建模过程一般都是从用例图(Usecase diagram)开始的。用例图描述参与者(对系统有影响的外部对象)与用例(系统功能或目标)的关系。
我们的系统目标单一,所以用例图也很简单。我们的用例图如下:
系统目标只有一个——快速读取数据。
这个图描述的是一个静态场景。即调用者需要快速读取数据。接下来我们就是要分析快速读取数据的过程。这样一个过程我们需要用动态图去描述。可以选择的动态图有活动图、时序图和写作图。因为尚不涉及状态问题,所以没有必要用状态图。个人习惯用活动图对用例分析,因为活动图对分支、判断、循环等流程概念的表达比其他图清晰,适合用于描述业务。
不过开始用活动图之前,我们需要确定这个过程中大概需要用到那些类。也就是对可能的功能作一个划分。这也是设计内容的部分。这部分因人而异。从我的角度看,我们需要有一个与调用者的接口,用于向调用者提供数据;一个读取数据的接口,用于获得数据。由于我们要做的是一个缓冲系统,所以还需要一个缓冲器。最后缓冲是在后台加载的所以还要有一个缓冲的加载线程。用这四个对象我们就可以描述快速读取数据的过程了。
在开始之前我们先准备好这四个类的类图:
接下来,是活动图的分析。
图中灰色部分标出了步骤编号。这一部分是缓存中有数据时读取数据的过程。
Step1:调用者调用接口读取功能。
Step2:读取接口从缓存中获取下一个数据。
Step3:从缓存中获得数据后进行判断是否为空(null)。
Step4:如果结果不为空则返回结果。
Step5:调用接口将结果返回给调用者。
这个分析过程中,我们添加了一个新的类,用来保存结果的数据类。这个类类图如下:
这个类是我们分析过程中对我们最初模型的补充。
接下继续分析缓存中没有数据的时候。
接上图,活动图的第二个片段
在缓存中获取的结果为空时,要启动后台线程加载新的数据。但是自启动之前要检查后台线程是否工作,如果后台线程正处于工作状态,说明上次加载数据的过程没有完成,要等待他结束。
Step1: 获得数据装载线程的状态。
Step2: 判断线程状态。
Step3: 当线程状态是工作的时候等待装载线程结束。
这个过程中我们也向需求模型中补充了一个新的类——线程状态类,用于保存线程的工作状态,类图如下:
接下来分析后续过程:
装载线程不活跃的时候,我们要同时做两件事情,其一是更换缓冲器,其二是让装载线程开始新的数据装载过程,将数据添加到刚用完的缓冲中去。
Step1: 在两个线程中异步进行。
--将当前缓冲设置为装载目标,并激活装载线程
--更换接口的读取设置
Step 2: 判断缓冲器是否为空。如果为空说明上次装载数据没有装载内容,也就是已经将资源读完,可以返回空值。
Step3: 返回空值
Step4: 如果新缓冲器不为空,重新获取值。
最后我们分析数据线程的活动:
数据装载线程被激活之后读取数据。数据装载结束之后通知等待读取结束的线程继续,同时自己回到等待状态。
Step1: 激活数据装载线程
Step2: 一个循环加载数据的过程。判断加载计数器的数值与装载线程额定装载数量是否相等来结束装载过程。装载过程是调用数据装载器加载资源并封装为结果数据。然后将数据压入目标缓冲。
Step3: 缓冲装载数据达到额定值之后,线程回到等待状态。
Step4: Step3同时异步进行,通知等待装载结束的线程继续执行。
这样整个数据读取过程分析结束。
整个活动图如下:
上图描述了整个通过异步的数据装载线程实现快速读取数据的功能。
在分析中我们也得到了这个过程中涉及的类:
活动图中,每个类占用一个泳道,泳道中的活动是这个类在整个过程中所要执行的功能。我们将这些活动和活动中涉及的数据作为成员函数和成员变量添加在我们得到的类模型中。最终类图如下:
需求分析过程结束。
4.需求分析总结
我们在上面使用UML图作为展示手段,描述了需求分析的过程。但是使用UML的目的并不在画图,而在于建立模型。下满我们总结模型的结构。
首先:
需求分析模型整体如上图,我们有一个参与者(调用者)和一个用例,在分析过程中得到了六个相关类。
其次:
对用例的活动分析之后我们的到了快速读取数据过程中涉及的各种活动、状态和数据。
最后:
将这些活动、状态和属性分配到各个类中,我们就获得了最终的类结构。
这就是我们的需求模型。