所谓需求分析,就是搞清楚客户到底要的是一个什么样的软件。无论这个软件是用于飞天登月的大型系统,还是仅供孩子们玩的游戏程序,需求分析永远都是我们开发工作的第一步。所以,当小陈接到老板下达的任务后,他做的第一件事不是立即修改程序代码,而是先进行需求的分析,搞清楚老板到底要的是怎样一个工资程序。
根据老板的抱怨(在实际的开发实践中,这往往来自前期的用户调查),这个工资程序必须能够输入员工的工资数据,而输入数据又包括直接从数据文件读取和手工输入;完成数据输入以后,这个程序还要对工资数据进行处理,包括统计最高的工资,以及根据员工的姓名对工资进行查询;最后,就是将所有的工资数据输出到文件,以便于下次直接读取。
经过这样的简单需求分析,小陈对老板想要的工资程序就比较清楚了。为了让这些需求更加清晰而直观,小陈将其绘制成了UML用例图,老板要的工资程序,不过就是实现了这些用例的工资程序。
最佳实践:全世界程序员都在说UML
UML(统一建模语言,Unified Modeling Language),一种描述软件的常用方式,它通过为软件建立模型,并通过一系列图(用例图、类图、活动图等)来直观地描述软件的结构和行为,从而让程序员对软件有一个清晰的认识和理解。因此,在具体实现一个软件之前,我们都使用它来描述我们即将开发的软件,以期在项目团队中达成对软件的共识。也正因为如此,整个项目团队中的成员,甚至是全世界的程序员,都必须掌握这门建模语言。
图6-13 工资程序的用例图
6.4.2 从问题描述中发现对象
完成程序的需求分析后,小陈明白了自己要做的是怎样的一个软件,接下来的问题就是怎么做了。按照面向对象思想解决问题的一般顺序,首先就是从问题描述中发现对象。而小陈知道,问题描述中的那些名词实际上就是对象。
按照“寻找对象就是寻找名词”的思路,小陈开始寻找这个问题描述中的名词。首先,遇到的第一个名词是工资系统(SalarySys)。然后就是该系统所管理的员工(Employee),因为级别的不同,员工又分为高级员工(Officer)和普通员工(Staff),这些就是整个问题中的名词,也就构成了整个问题所涉及的对象。
从问题描述中除了可以找到对象之外,还可以发现对象之间的各种关系:工资系统管理员工对象,它们之间是一对多的关系;同时,高级员工和普通员工同属于员工,这就表示它们应该有着共同的基类,都是从员工类所派生出来的。图6-14描述了整个问题中的对象及对象之间的关系。
图6-14 工资程序中的对象及对象之间的关系
6.4.3 分析对象的属性和行为
在找到对象之后,就可以进一步分析这些对象所拥有的属性和行为,然后利用面向对象的封装机制将其封装成具体的类。首先,分析这个问题中最基础的员工类Employee。根据老板的要求,为了找到工资最高的员工,我们必须记录每个员工的姓名(m_strName);为了根据在职时间(现在时间减去入职时间)动态地计算员工的工资,我们必须记录员工的入职时间(m_nYear);员工有级别的差别,各个级别的员工工资计算方式不同,应该有一个属性级别(m_nLevel)来记录。所以这个对象必需的属性就是姓名、入职时间和级别。
分析了员工类Employee的属性,那么它又该拥有什么样的行为呢?类的行为都是用来完成需求分析中的用例的,所以,Employee类的行为跟它要完成的用例密切相关。为了完成“计算最大值”用例,它应该有一个计算工资的行为(GetSalary()),可以根据员工的在职时间动态地计算员工的工资。但是,Employee类作为具体的员工类Officer和Staff的基类,并不知道工资的具体计算方法,所以这个行为只是一个接口而已,需要留待它的派生类来具体实现,所以在Employee中这个函数应该是一个纯虚函数;而要计算工资,它又必须知道员工的在职时间,所以它还必须有一个获得在职时间的行为(GetWorkTime());同时,为了完成“查询工资”这个用例,程序需要知道员工的姓名,所以员工类应该提供一个获得名字的行为(GetName());最后,为了完成“输出数据到文件”的用例,Employee类还必须提供获得员工级别(GetLevel())和入职年份(GetYear())的行为,从而可以获取员工的信息并将其输出。
经过这样的分析,小陈得出了员工类Employee应该具备的属性和行为。为了记录自己的分析结果,让结果一目了然,小陈将分析结果画成了UML类图:
图6-15 Employee类的属性和行为
具体的员工类Officer和Staff是Employee的派生类,在Employee类的基础上,这两个具体的员工类并没有额外的需要描述的内容,所以它们不需要新添加属性,只需要从基类继承已有的属性即可。而至于行为,具体的员工类需要负责具体的工资计算和返回不同的员工级别,所以它们需要实现基类中的GetSalary()和GetLevel()这两个虚函数。经过这样的分析,Officer和Staff类应该具备的属性和方法就很清楚了。小陈将它们用如下的UML类图来表示:
图6-16 Officer和Staff类的属性和行为。
按照同样的方法,小陈接着分析用于管理这些员工对象的SalarySys类。为了保存和管理多个Employee对象,SalarySys类必须有一个数组来保存这些对象,而为了应用面向对象的多态机制来动态地计算员工工资,数组中保存的不应该是这些对象本身,而应该是指向这些对象的指针;同时,数组只是表示了SalarySys所能够保存的最多的对象指针,但是并不是数组中的每个指针都是有效的,具体保存了多少个指针还不清楚,我们还必须用一个属性来表示当前有效的指针的个数(m_nCount);另外,SalarySys需要从文件读取数据,最后还需要将数据写入文件,所以它还需要一个记录数据文件名的属性(m_strFileName)。
在SalarySys类的行为上,小陈还是同样从它要完成的用例来分析。根据他之前对这个程序进行的简单需求分析,SalarySys首先需要完成“输入数据”这个用例,而这个用例又包含了“从文件读取”和“手工输入”这两个用例,这就要求SalarySys类应该具有从文件读取数据(Read())和让用户手工输入(Input())的行为;完成“输入数据”之后,就是“处理数据”,它也同样包括了“计算最大值”和“查询工资”两个用例,这就要求SalarySys类具有查找所有工资数据中的最大值(GetMax())和根据用户输入的姓名查询相应工资信息(Find())的行为;最后,就是“输出数据”这个用例,它要求SalarySys具有将所有工资数据保存到数据文件(Write())的行为。
分析完成之后,小陈同样将分析结果绘制成了UML类图:
图6-17 SalarySys类的属性和行为
原文文章出自红树林平台 http://www.hongshulin999.com 原创发布转载请注明出处