你好,C++(38)从问题描述中发现对象的属性和行为 6.4 工资程序成长记:类与对象(上)

2015-04-29 来源: 你好,C++ 发布在  http://www.cnblogs.com/nihaoCPP/p/4459922.html

6.4  工资程序成长记:类与对象

“夜半三更哟,盼天明;寒冬腊月哟,盼春风。若要盼得哟,涨工资,岭上……”自从上次老板许诺给小陈涨工资以后,一转眼又过去几个月了,可是涨工资的事一点动静都没有。小陈只好天天哼着这首歌,自己安慰自己,天总会亮的,春天总会来的,而工资也总会涨的。这天,小陈正在哼这首歌,没想到老板又让他去办公室。小陈心中那个高兴啊,心想,盼星星盼月亮,总算盼到了这一天啊。

于是,小陈赶紧来到老板的办公室。可是,当他一进办公室,看到老板那阴云密布的脸就知道情况不妙。果然,老板一见小陈就抱怨起来:

“小陈啊,你这个工资程序怎么搞得嘛?你是知道的,我们公司的工资是按照员工的入职年数来计算的,并且高级员工和一般员工的计算方法不同。可是过了一年了,每个人的工资还是第一次输入的数据,没有变化嘛!还有还有,你这个程序只能找到最高工资的人的序号,只知道序号有什么用啊,我要知道名字,名字,这样我才好直接把他给开了啊……”

一听老板这一通抱怨,小陈心凉了半截,心想这次涨工资肯定又没戏了。于是有气无力地说:

“老板,你别着急,程序就是这样不断改进不断完善的。让我回去按照你的要求修改修改,保证让你满意。”

就这样,在老板那里挨了一顿训之后,小陈又带着老板的新要求回来了。小陈简单地分析了一下老板的新要求:要让每个员工的工资动态计算,而员工又分为高级员工和一般员工两种,每种员工的工资计算方法各不相同。在统计的时候,不仅需要给出最高工资者的序号,还要给出姓名的信息。这些新的要求看起来还挺复杂的,小陈正挠头细想解决之道,突然灵光一闪:这个问题,正好可以用C++中的面向对象思想来解决啊——利用封装机制,可以把员工的序号、入职年份、姓名、工资等信息封装成员工类,这样在统计得到最高工资的员工序号的同时也就得到了对应的姓名;利用继承机制,可以从员工类派生出高级员工类和一般员工类,再配合多态机制,就可以实现对两类员工的工资采取不同的计算方式了。想到这里,小陈不由得一拍小腿,心中感叹,面向对象思想在解决复杂问题时果然威力无比啊!巧的是,小陈这段时间刚好学过了C++中用类来体现面向对象思想,于是他决定用类来对这个工资程序进行改写。

6.4.1 需求分析:老板要的是这样一个工资程序

所谓需求分析,就是搞清楚客户到底要的是一个什么样的软件。无论这个软件是用于飞天登月的大型系统,还是仅供孩子们玩的游戏程序,需求分析永远都是我们开发工作的第一步。所以,当小陈接到老板下达的任务后,他做的第一件事不是立即修改程序代码,而是先进行需求的分析,搞清楚老板到底要的是怎样一个工资程序。

根据老板的抱怨(在实际的开发实践中,这往往来自前期的用户调查),这个工资程序必须能够输入员工的工资数据,而输入数据又包括直接从数据文件读取和手工输入;完成数据输入以后,这个程序还要对工资数据进行处理,包括统计最高的工资,以及根据员工的姓名对工资进行查询;最后,就是将所有的工资数据输出到文件,以便于下次直接读取。

经过这样的简单需求分析,小陈对老板想要的工资程序就比较清楚了。为了让这些需求更加清晰而直观,小陈将其绘制成了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类的属性和行为

相关文章