7.1.3 建立对象模型
面向对象分析首要的工作,是建立问题域的对象模型。对象模型是三个模型中最关键的一个模型,这个模型表示静态的、结构化的、系统的“数据”性质。它是对客观世界实体中对象及其相互之间关系的映射,描述了系统的静态结构。这种静态数据结构对应用细节依赖较少,比较容易确定,当用户的需求变化时,静态数据结构相对来说比较稳定。因此,用面向对象方法开发绝大多数软件时,都要首先建立对象模型,然后再建立另外两个子模型。
1.发现和定义类和对象
利用面向对象软件技术可以显著提高软件开发的质量和效率,但它必须在正确识别了对象集合的基础上才得以体现。对于一个给定的应用领域,一个合适的对象集合能够确保软件的可重用性,提高可扩展性,并能借助面向对象的开发模式,提高软件开发的质量和效率。没有科学的发现和定义类对象的客观方法,就不能充分地发挥面向对象设计程序方法的优势。
在对象识别中最为关键的是正确运用抽象原则。面向对象分析用对象来映射问题域中的事物,但并不是问题域中的所有事物都需要用对象来映射。系统分析员应该紧密围绕系统责任这个目标去对问题域中的事物进行抽象,对这些事物进行取舍,识别出反映系统特征的对象。取舍的标准是看问题域中的事物及其特征是否与当前的目标有关。
首先要舍弃与系统责任无关的事物,保留与系统责任有关的事物。其次,还要舍弃与系统责任有关的事物中与系统责任无关的特征。判断事物及其特征是否与系统责任相关的准则是:该事物是否向系统提供了一些服务或需要系统描述它的某些行为,同时还要考虑将问题域中的事物映射成什么对象及如何对对象进行分类。
为了尽可能地识别出系统所需的对象,在系统分析的过程中应采用“先松后紧”的原则。系统分析员应首先找出各种可能有用的候选对象,尽量避免遗漏,然后对所发现的候选对象逐个进行严格的审查,筛选掉不必要的对象,或者将它们进行适当的调整与合并,使系统中的对象和类尽可能地紧凑。在寻找各种可能有用的候选对象时,主要的策略是:从问题域、系统边界和系统责任这些方面出发,考虑各种能启发人们发现对象的因素,找到可能有用的候选对象。
(1)在问题域方面,可以启发分析员发现对象的因素包括人员、组织、物品、设备、事件、表格、结构等。
(2)在系统边界方面,应该考虑的因素包括人员、设备和外部系统,它们可以启发分析员发现一些系统与外部活动所进行的交互,并处理系统对外接口的对象。
(3)对系统责任的分析是基于对象识别的遗漏的考虑,对照系统责任所要求的每一项功能,查看是否可以由已经找出的对象来完成该功能,在不能满足要求时增加相应的对象,可以使系统分析员尽可能完全地找出所需的各种对象。
下面来发现上述的教务信息管理系统中的类对象。首先在用户需求中分检出候选的类对象。通常需要通读需求报告,在问题域中发现其中的名词,将其分捡出来作为候选的类对象。在教务信息管理系统中,可能作为候选类对象的有:系统、用户、新生、系统管理员、学生、基本信息、姓名、性别、年龄、身份证号、家庭住址、身高、学号、学生证号、学校领导、学生信息、相关部门、新专业、学校、社会、课程信息、课程编号、所属专业、课程名称、开课学期、学时、学分、授课教师、专业、班级、报表、网络、选修课、时间、数量、人数、考试、考试成绩、账号、密码、账号信息。
显然,仅通过一个简单、机械的过程不可能正确地完成分析工作。上述分析仅仅帮助我们找到一些候选的类和对象,接下来应该严格考察每个候选对象,从中去掉不正确的或不必要的对象,仅保留确实应该记录其信息或需要其提供服务的对象。
1)冗余
对于若干个表达相同信息的类和对象,应只保留那些在问题域中最富有描述力的名称。例如,在教务信息管理系统中,新生和学生、学号和学生证号都表达了相同的信息,可以去掉新生和学生证号,只保留学生和学号。
2)无关和模糊
在初步分析时,有一些与问题无关的对象和需求陈述中使用到的一些含义比较模糊、泛指的名词会被挑选出来,这些对象和名词,要么与当前要解决的问题无关,要么是可有可无的。因此,应该把这些与当前要解决的问题无关和含义比较模糊的候选类对象去掉。例如,在教务信息管理系统中,系统、相关部门、学校、社会、网络、基本信息等是一些与解决问题无关或比较笼统的候选类对象,可以将它们去掉。
3)属性
对象是用属性来描述的,若有些名词只是其他对象的属性描述,则应该把这些名词从候选类对象中去掉。当然,如果某个性质具有很强的独立性,则应把它作为类而不是作为属性。例如,在教学信息管理系统中,姓名、性别、年龄、身份证号、家庭住址、身高、学号、学生证号都属于学生的特征,作为学生的属性即可。
4)操作
在需求陈述中,有时可能使用一些既可作为名词又可作为动词的词,此时应根据它们在本问题中的含义来决定它们是作为类还是作为类中定义的操作。例如,通常把电话“拨号”当做动词,当构造电话模型时,确实应该把它作为一个操作,而不是一个类。但是,在开发电话的自动记账系统时,把“拨号”作为重要的一个类,因此,它有自己的日期、时间、受话地点等属性。总之,当一个操作具有属性而需要独立存在时,应该作为类对象而不是作为类的操作。
5)实现
在分析阶段不应该过早地考虑怎样实现目标系统。因此,应该去掉仅与实现有关的候选的类与对象。在设计和实现阶段,这些类与对象可能是重要的,但在分析阶段过早地考虑它们反而会分散我们的注意力。
综上所述,在教务信息管理系统中,筛选的候选类对象包括学生、教师、账号、课程、成绩、班级、专业和选修课,其示意图如图7-1所示。

图7-1 教务信息管理系统初步类图
2.确定属性
1)为何确定属性
属性是用于描述类对象的特性的。一个属性是一个数据项(状态信息),类中对象都有相应的值(状态)。目前,面向对象分析模型越来越专业化,而且更加详细,每个类对象都由属性描述,而属性则按照类对象的规范来描述。属性放在类对象中表示符号的中间部位。
在面向对象分析中,属性用于反映问题和系统的任务。属性能帮助我们更深入、更具体地认识类对象和结构。换句话说,属性能为类对象和结构提供更多的细节。因此,在一个系统中,确定属性是非常重要的。
2)如何确定属性
可以从如下角度确定对象应具有的属性:
(1)按照一般常识,对象应该具有哪些属性;
(2)在当前问题域中,对象应具有哪些属性;
(3)根据系统责任的要求,对象应具有哪些属性;
(4)建立该对象是为了保存和管理哪些信息;
(5)为了在服务中实现其功能,对象需要增设哪些属性;
(6)是否需要增设属性来区别对象的不同状态;
(7)用什么属性来表示对象的整体-部分联系和实例连接。
3)审查与筛选
对于找到的对象属性,还应进行严格的审查和筛选,才能最终确定对象应具有的属性。在审查和筛选中,应考虑的问题有:
(1)该属性是否体现了以系统责任为目标的抽象;
(2)该属性是否描述了该对象本身的特性;
(3)该属性是否破坏了对象特征的“原子性”;
(4)该属性是否已通过类的继承得到;
(5)该属性是否可以从其他属性推导得到。
4)命名
在确定了对象属性之后,应对各个属性命名加以区别。属性的命名在词汇中的使用方面与类的命名原则基本相同。在工作的最后,应在类描述模板中给出每个属性的详细说明,包括属性的解释、数据类型、属性所体现的关系、属性的实现要求等。
在教务信息管理系统中,以学生信息类为例添加内部属性,从需求中获得的学号、姓名、年龄、家庭住址、性别、身高、身份证号等属性,但是身高属性对本系统来说显然是没有意义的,因此去掉。学生信息类还应包含学生所属的专业编号、班级编号,用于记录学生所在的专业班级信息,同时还应该增加学生的入学年份属性,这样便于了解学生的学籍情况。又如需求中的课程编号、所属专业、课程名称、学时、学分、任课教师都属于课程的特征,所以作为课程类的属性。但由于每学期开设的课程不同,所以再增加开课学期这一属性。在这里将类中的所有属性设置为私有的(private),这样能够更好地保证封装性,系统内其他对象对该类属性的访问只能通过对应的方法。此时,类图中所有的类都是只有属性而没有方法。如图7-2所示为学生、课程和班级的类图。

图7-2 学生、课程和班级的类图
3.定义对象的服务
对象是描述它的属性的数据和作用在其数据上的操作(即服务)的封装体。在对象模型中,已经确定了类中应有的属性,但是还需要定义类中应有的服务。从下面几个方面考虑定义类中应有的服务。
1)发现需求中的动词
系统分析员之前在系统需求中分检出相应的名词作为候选的类对象。可以使用类似的方法,在系统需求中分检出相应的动词,作为类中可能使用的服务,通过这种方法能够发现类对象的一些服务。
2)访问对象属性的操作
在对象模型中,对类中定义的每个属性都是可以访问的,应该提供访问这些属性的服务。因此,需要定义访问这些属性的读/写操作。这些操作在对象模型中没有显式表示出来,但隐含在属性内。
3)来自事件驱动的操作
发往对象的事件驱动修改对象状态(属性值),对象被驱动后的行为可定义为一个操作,并通过执行该操作提供相应的服务。也就是说,在对象接收到事件后,在事件驱动下完成相应的服务。
在教务管理系统中,在学生信息类中,我们拥有添加学生信息、修改学生信息、删除学生信息和查询学生信息等服务。在账号类中,我们拥有添加登录、修改密码、新建账号、分配权限等服务。图7-3列出了添加了服务的完整类图。
4.确定关联
在确定了问题域中类对象的属性和服务之后,接下来的工作就是分析确定对象之间的关联关系。所谓关联是指两个或两个以上对象之间的相互依赖、相互作用的关系,即通过对象属性来表示一个对象对另一个对象的依赖关系。在OOA模型中,关联关系是一种结构关系,表达模型元素之间的一种语义联系,它是对具有共同的结构特性、行为特性、关系和语义的描述。为了建立关联关系,应进行如下分析:
(1)分析对象之间的静态联系;
(2)分析连接的属性和操作;
(3)分析关联关系的多重性;

图7-3 添加了服务的完整类图
(4)分析多元关联的多对多关联等异常情况的处理。
如在建立关联关系的过程中可能增加一些新的对象类,应把这些新增的类补充到类图中,并建立它们的类描述模板。由于仅依靠OOA模型中的一条关联线并不能详尽地表达出该连接,所以需要附加必要的详细说明。
在教务信息管理系统中,学生与课程之间的关系为选修关系,一个学生可以选修一门或多门课程,每门课程可以被多个学生所选修,所以课程和学生两个类之间的多重性为多对多的关系。多对多的关联相对比较复杂且不好描述,通常情况也会将这种关联关系描述为类,称为关联类。通过这样的方式,能够让两个类更好地关联和导航。在本系统中,选修信息这个类就是学生与课程两个类之间的关联关系,用于描述和映射两者之间的选修关系。通过选修信息这个类,将学生与课程的多对多的关联关系,转换为一对一的关联关系,选修信息类作为关联类,关联类通过一条虚线与关联连接,如图7-4所示。
教师与课程之间具有讲授的关系,这种关系也是多对多的关系。因此也增加一个关联类,即课程安排,用于记录课程的安排情况。通过这种方式将教师与课程之间的关联关系,转换为一对一的关联关系。这个思路与学生和课程之间的关系是一致的。
此外,账号能够支持学生的登录,也能够支持教师的登录,还可以支持系统管理员的登录。无论是学生还是教师实体相关的信息都需要系统来进行管理,而学生和教师登录到系统的时候,往往能够浏览和操作的功能一般也都与自身相关。也就是说,他们在使用账号登录时,必须通过账号找到对应的学生或教师的相关信息才行。因此,账号与学生信息、教师信息之间具有关联关系。它们的多重性都是1对0..n。也就是说,一个账号可以与0个或多个学生信息实体相对应,而每个学生对象必然有一个账号与之相对应。账号与教师信息实体之间的关系也是如此,这里不再赘述。图7-5所示为增加了关联关系的系统类图。为了方便起见,服务部分省略掉,没有进行显示。

图7-4 学生类与课程类的关联关系

图7-5 添加了关联关系的系统类图
5.识别继承关系
确定了类中应该定义的属性之后,就可以利用继承机制共享公共性质,并对系统中的类加以组织。继承关系的建立实质上是知识抽取过程,它应该反映出一定深度的领域知识,因此必须要有领域专家密切配合才能完成。通常,许多归纳关系都是根据客观的分类模型建立起来的,只要有可能,就应该使用现有的概念。
一般来说,可以使用两种方式建立继承关系。
(1)自底向上:抽象出现有类的共同性质,泛化出父类,这个过程实际上模拟了人类归纳思维过程。
(2)自顶向下:把现有类细化成更具体的子类,这模拟了人类的演绎思维过程。从应用域中常常能明显看出应该做的自顶向下的具体化工作。
在教务信息管理系统中,比较明显的继承关系的是课程信息与选修课之间的关系,选修课具有课程的全部特征,包括课程编号、课程名称、学时、学分、授课教师编号、开学学期、所属专业编号等属性,还有添加课程、修改课程、删除课程、查询课程等服务(这里为了表达清晰,服务被忽略掉了)。选修课类可以继承课程类,如图7-6所示。

图7-6 课程类和选修课类的继承关系
6.反复修改
仅仅经过一次建模过程很难得到完全正确的对象模型。事实上,软件开发过程就是一个多次反复修改、逐步完善的过程。在建模的任何一个步骤中,如果发现模型的缺陷,都必须返回到前期阶段进行修改。由于面向对象的概念和符号在整个开发过程中都是一致的,因此远比使用结构分析、设计技术更容易实现反复修改、逐步完善的过程。
实际上,有些细化工作(如定义服务)可以在建立了动态模型之后进行。
在实际的工作中,建模的步骤不一定严格按照前面讲述的次序进行。分析员可以将合并几个步骤的工作放在一起完成,也可以按照自己的习惯交换前面讲述的各项工作的次序,还可以先初步完成几项工作,再返回来加以完善。但是,如果是初次接触面向对象方法,则最好先按照书本所述次序,尝试用面向对象的方法,开发几个较小的系统,取得一些实践经验后,再总结出更适合自己的工作方式。