面向对象系统分析与设计专题<6>__类图

1 类图

用例叙述详细描述了系统将对每一个事件做出的响应,这为后续工作中系统及其环境间交互建模提供了可能。同时,为了理解应用程序的问题域以及定义用户对已存储数据的需求需要建立概念模型,描述系统中各种对象以及它们之间的各种关系。面向对象方法的核心工作是分析和设计对象以及类,这是一个迭代过程。类作为一个整体,代表了早期系统问题域的概念模型,随着其含义的逐步明确,概念模型不断演进,发展到实现模型,直接对应到程序语言的实现框架,最后根据实现模型进行类编程实现。

尽管在面向对象分析与设计阶段都使用类图,但它们的抽象层次是不一样的。分析层次的类图通过描述应用程序领域内的有用名词概念以及它们之间的相互关系,从而详细说明用户对新系统的需求,我们称之为域模型。在这个阶段,通过问题域中概念及其属性、关联的建模来获得对问题域的最小化、非冗余、全面的理解。

 

1 分析层次类图——域模型

生成类图是面向对象系统开发过程的第3个基本步骤。分析层次类图——域模型是一个问题域结构的静态模型。它建立的是现实世界中概念的模型,而不是软件单元的模型。一个域模型由以下部分组成:概念;概念的属性;概念间的关联。

域模型只包括属性——不包括行为。它也不包括与实现用户界面相关的概念,如屏幕输出或按钮。用户界面实现问题是一个设计行为。域模型是一个关于概念及其属性、关联的视图,它可以反映模型内各组件之间的关系。

“域模型”是统一过程(Unified Process)中的术语。就统一建模语言(UML)来说,一个域模型就是一个类图。UML对所有的类图采用相同的约定,无论是分析模型、设计模型,还是实现模型。图1显示了一个简单的域模型的例子。

clip_image002

图1 问题域模型的例子

概念,也称为概念类、分析对象,是对一件事情、一个人或者一个理念的抽象。如图1所示,用一个矩形表示概念。概念的名字出现在矩形框最顶端的空格里。如果概念名用英文单词表示,每个单词的首字母大写,如:Course,DepartmentClassShedule。

属性是概念的被命名的特征,可以具有值。概念的属性列在矩形框中间的空格中,属性名由小写字母开头而且不包含空格,属性名中所有的附加单词首字母大写。

       一个关联说明了问题域的两个概念间存在着重要的关系。用连接一对概念的直线来表示关联,用关联的名字标记这条线。关联的名字应该是动词短语,它们用英文表示时首字母大写。关联是双向的,可以从任何一个方向进行理解,但标记双向关联的名称只能应用于一个方向。例如:关联“选择”指的是概念“学生”选择“课程”,而不是“课程”选择“学生”,从另一个方向理解时是“课程”被“学生”选择。关联的末端注释表示数目的标签以说明多样性,即一个概念有多少个实例与另一个概念的某个实例相关联。

       建立域模型,需要发现其各个组件并把它们加入到模型中,我们可以根据业务事件表和用例模型,采用一次一个用例的方法来建立域模型。实践中,概念、属性和关联在被发现的同时被加入到域模型中。我们采用四个步骤来进行:定义并添加概念;添加属性;识别并添加关联;一致性检验。

 

2 定义并添加概念

       概念是对问题域中一个人、事物或者理念的抽象。人被抽象为像经理、顾客、学生、职员等角色。事物的例子像客房、图书、商品等。理念是抽象的概念,如:航线、银行账户等。发现概念是面向对象开发中重要的一步,因为分析、设计和编码将使用它们作为主要元素。

1.发现概念

发现概念需要有关面向对象范型的深入知识和技术,以及将其应用到所要开发的系统的能力。在使用用例模型完成了需求捕获和描述后,已经对问题域、系统边界和系统责任进行了分析,把用户的需求落实到了各个用例之中。问题域、系统边界和系统责任是发现概念的基础,考虑问题域,侧重于客观存在的事物与系统中概念的映射;考虑系统边界,可启发分析员发现一些与系统边界以外的参与者进行交互并处理系统对外接口的概念;考虑系统责任,侧重于系统责任范围内的每一项职责都应落实到某个(某些)概念来完成。

作为一种策略,可通过查找问题域中的名称和名词短语来发现概念。参考用例叙述可以找到这种单词和短语,考虑用例“提交公修课班级计划列表”(如图2),它的描述包括以下名词短语:系、公修课、班级计划列表、班级、学期、系编号、课程编号、班级编号、最大座位数、上课地点、教师编号、教师、课程。

用例: 

提交公修课班级计划列表 

参与者:

目的:

记录系公修课班级计划

概述:

系提交其将为下一个学期提供的公修课班级计划。每个班级的详细信息(系编号、课程编号、班级编号、最大座位数、上课地点以及教师编号)均记录在系统中。

类型:

基本

前置条件:

课程与教师信息已经输入到系统中。

后置条件:

系公修课班级计划保存在系统中。

特定要求:

系必须在输入每个小组后,于10秒内获得系统响应。

事件流

参与者操作

系统响应

1. 该用例在系提交其学期公修课班级计划时开始。

 

2. 该系提供计划中每个小组的系编号、课程编号、班级编号、最大座位数、上课时间、上课地点以及教师编号。

3. 记录系公修课班级计划信息。

4. 完成计划输入后,该系提示该计划完成。

 

候选事件流

 

第3行:

输入了无效的系编号和课程编号。 提示错误。返回到步骤2。

输入了无效的上课时间。提示错误。返回到步骤2。

输入了无效的上课地点。提示错误。返回到步骤2。

输入了无效的教师编号。提示错误。返回到步骤2。

     

图2提交公修课班级计划列表的完整用例叙述

使用这种方法时需注意,并非所有的名词或名词短语都是概念名,如系编号、课程编号、班级编号等可能是属性名,还有一些可能也不作为概念。

 

2.在域模型中包含概念的准则

哪些名词应该包含在域模型中,是一个重要和困难的决策,实践中可以参考以下原则:

(1)当系统需要存储与某概念相关的数据以备响应未来某个事件时,应该将概念包含到域模型中。

如为了记录系公修课班级计划,系统必须知道系编号、课程编号、教师编号,以及其他属性。它需要知道已输入系统的课程和教师信息。从中,我们了解需要包含系、课程和教师的概念。

(2)并非每个参与者都要为一个概念,验证标准是系统是否需要记录参与者的属性。

系在“提交公修课班级计划列表”用例中是一个参与者,同时也是一个概念。但这并不意味着每个参与者都必须是概念,要分析系统是否需要记录参与者的属性,如果不需要,我们不把它作为概念包含在域模型中。

(3)区分概念和描述概念的属性。

概念和属性都是域模型的组件,但它们扮演着不同的角色。参照系的概念,系编号是一个属性。上课时间、上课地点与课程的某个班级相关,看起来像是班级的属性。课程编号是概念课程的一个属性,教师编号是概念教师的一个属性。我们通过一个描述课程的属性间接地发现了课程这个概念,通过一个描述教师的属性间接地发现了教师这个概念。类似地,班级编号是概念班级的一个属性。

系统将来需要了解的那些表示系统输出的概念,需要作为重点来考虑。这些概念的属性作为用户界面设计的基础。任何需要生成输出以响应某个事件的数据元素必须包含在系统输入中,必须是域模型的属性,或者必须能从系统的输入和属性中推导得到。

现在我们已经定义了系、课程、班级、教师这些与用例“提交公修课班级计划列表”相关的概念,如图3所示。当前,这些概念是彼此无关的,属性也没有标示出来,只是个初始的模型。

clip_image002[15]

图3 初始的概念

 

3 添加属性

属性是概念的某个被命名的特征,它可以有值。定义属性的活动是要把每个概念的特征抽出来,分别作为该概念的单独的属性。

 
1.属性与值

不同概念具有不同的概念名,多数情况下,也具有不同的属性集。但,有些概念可能具有相同的属性名,例如学生(number, name, address, major,classlevel),教师(number, name, address, title),学生和教师的编号、名字、住址这些属性名相同。

当需要惟一的定义某特殊概念的属性时,可以用概念名作为属性名的前缀,格式如学生(studentNumber, studentName, studentAddress, major,classlevel),教师(professorNumber, professorName, professorAddress, title),这被称作限定属性名。

为概念的每个属性赋值可以充分确定一个实例,如(09562453,郑飞,中五302,数学,大三)。一个实例是所有与这个概念相关的实例集合中的一员。

可以从两个角度看一个概念。一个是抽象——它命名了概念并枚举了其属性,另一个是具体——通过为属性赋值来区分概念的实例。

 

2.发现属性

寻找属性通常是为了在概念中存储必要的描述数据。前面所发现的,在问题域的描述中寻找名词短语的方法为我们带来了属性名和概念名。在寻找概念的时候,可以发现属性系编号、课程编号、班级编号、最大座位数、上课地点、上课时间以及教师编号。

另一个策略是查看每个系统输入中的数据。如果系统输入中的数据必须被保留以备将来使用,它们将作为属性,同时要分析它们描述的是哪些概念的特征。

完整识别的情况下,所需的系统输出都能从域模型的属性中生成。如果不能,说明遗漏了一个或多个属性(也可能是概念),需要继续分析问题域。

 

3.在域模型中包含属性的准则

(1)当系统需要记录某属性值以响应某个事件时,应该在域模型中包含此属性。

(2)如果不能确定是属性还是概念,就在初始模型中将其定义为概念。

(3)不要用属性来记录概念间的关系,用关联来代替。

(4)如果一个属性能从模型中其他的属性推导得到,不要包含它。

尝试寻找那些值不变,但能用来计算其他所需值的属性。班级中最大座位数是比可用座位数更好的属性,可用座位数可以通过最大座位数和目前已登记学生人数计算得到。

图4加入了一些域模型当前发现的属性。

clip_image002[17]

图4带有属性的概念

 

4 定义概念间的关联

前面已经定义了概念以及属性,这里定义概念间的关联,以在域模型中添加概念之间的关系。这样,各个概念才能构成一个整体的、有机的域模型。运用关联可以表现概念间的相互关系和交互情况,关联扩展了域模型,为用户的问题域提供了一个宽广的相互连接的视图。

 

1.关联与关联实例

关联指明了问题域中两个概念间存在着的重要关系。一个概念在逻辑上与另一个概念相连接。正如概念具有实例,关联也具有实例。关联在域模型中用一条连接两个相关概念的直线表示,我们需要根据它在问题域中的含义为它命名。

考虑关于概念学生和班级的一个关联,把它称为“登记到”。当概念学生的一个实例与概念班级的一个实例结对时就有一个“登记到”关联实例产生。例如:学生实例(09562453,郑飞,中五302,数学,大三)与班级实例(01,周三下午1、2节,理科楼303,35)相关联,产生一个关联实例。大多数关联是双向的,只有一个方向的关联要在域模型中表明。通常为关联命名,便于在图中从左到右以及从上到下地阅读。

在图5中,“登记到”从概念学生指向概念班级,可读成“学生登记到班级”。图8-32中,显示的是从概念班级指向概念学生的关联,从右向左可读成“班级包括登记的学生”。

clip_image002[19]

图8-31 关联从概念学生指向概念班级

clip_image004

图5 关联从概念班级指向概念学生

2.多样性

关联的多样性定义了一个概念有多少个实例与另一个概念的某个实例相关联。代表关联的直线也显示了概念中可以用于关联的最大和最小实例数目。因此,多样性的值成对地显示在关联的尾部——符号n表示零或多个,或者写成0﹍n。最小值是左边的那个数字,通常是0或1;最大值是右边的那个数字,通常是1或n。

在图6中,单向关联“登记到”(从学生到班级)是一到多,即一个学生登记零或多个班级。在图6中,关联“包含登记的”(从班级到学生)也是一到多,即一个班级可以被零或多个学生登记。现实情况下,每个班级有很多学生登记,同时每个学生登记了多个班级,如图6所示。我们把双向都为一到多的关联被称为是多到多的关联。

clip_image006

图6 学生和班级之间的多到多关联

3.在域模型中包含关联的准则

可以使用普通关联列表的方法帮助发现关联,表7使用了这样的列表。

当系统需要记住某关联以响应某个事件时,应在域模型中包含此关联。例如,在学生选课系统中,必须记录某个具体学生选择了某个具体的班级。

表7 普通关联列表

关联类别

示例

概念A是概念B的物理组成部分

屏幕—显示器

车架—自行车

A在物理上包含于B

货品—集装箱

乘客—客车

A在逻辑上包含于B

课程—学期课程表

A是业务交易B中的一个产品

销售项目—销售

A是B的一个成员

学生—社团

A是B的一个分组

系—大学

A与事务B相关

顾客—购物

读者—借书

4.整体—部分关联

客观世界中,事物之间的“整体—部分”关联很常见。整体—部分关联使得域模型中一个概念与参与构成的概念间发生联系,例如:一张大学班级计划和其中的项目,一个社团与它的成员,一个计算机和它的组件。为了描述整体—部分关联,可以用“有一个”、“包含”(从整体到部分)或者“是……的一部分”、“是……的成员”(从部分到整体)这样的词汇。UML提供了整体—部分关联的两个形式——聚合与组合。

(1)聚合

聚合是表示整体的概念和表示部分的概念之间的“整体—部分”关系。聚合关系中,代表整体的概念被称为聚合体;代表某个部分的概念被称为要素。聚合体和要素相互独立,一个要素可以同时是多个整体的组成部分。例如,一个学生可以同时是几个社团的成员。

聚合的数学性质包括如下两个方面:

①非对称性,若对象A是对象B的一部分,那么对象B就不能是对象A的一部分;

②传递性,若对象A是对象B的一部分,对象B是对象C的一部分,那么对象A是对象C的一部分。

把聚合关系表示成一条一端带有空心菱形的线段,菱形紧靠着代表整体的概念。多样性同时显示在聚合的两个端,图8-34显示了聚合的一个例子——拥有一名指导老师和众多成员的学生社团。

clip_image008

图8-34 一个聚合关系的例子

(2)组合

组合表示的也是整体和部分之间的关系。代表整体的概念被称为组合体,代表某个部分的概念被称为组件。

组合是比聚合更具限制性的关联。组合关系中,组合体不能独立于它的组件而存在。一个组件在一个时刻只可能是某一个组合体的组成部分。

当为一个组合关系建立模型时,关联底部为实心菱形并仅靠着代表总体的概念,多样性要在组件的尾部标示,组合体末端的多样性值都假定为1。图8-35显示了一个组合关系的例子——一个计算机有主机、显示器、键盘和鼠标构成。

clip_image010

图8-35 一个组合关系的例子

聚合和组合是整体—部分关联中很重要的两个概念,但也是比较容易混淆的概念,实际应用时开发人员需要根据需求分析描述的上下文来确定是使用聚合关系还是组合关系。表8-6总结了聚合关系和组合关系的区别。

表8-6 聚合关系与组合关系

 

聚合关系

组合关系

总体的名称

聚合体

组合体

组成部分的名称

要素

组件

组成部分

可能有不同类型

通常是相同类型

存在状态

可能独立于组成部分存在

不能独立于组成部分存在

组成部分可能从属于多少个整体

组成部分可以同时从属于多个聚合体

组成部分只能同时从属于一个组合体

5.一般到特殊的关联

应用领域中,一个概念基于一个或多个公共属性的不同值可以被划为子分类,这些子分类可能是互斥的或交迭的。例如,基于用户类别这个属性,概念用户可以分为管理员和一般用户。特别是某些功能仅限于管理员时,我们希望在系统中显示这种区别。

高级别的概念称作一般化(或超类型),低级别的概念称为特殊化(或子类型)。子类型是超类型的特殊化或改进。可以用“……是……”,“……是一种……”,“……是一类……”,这样的语句把子类型和超类型关联起来,来说明一般到特殊的关联。

图8-36显示了域模型中描述的一个一般到特殊的关联的例子。子类型和超类型用一个带箭头实线相连,末端是一个空心的三角箭头,指向超类型。可以被读作研究生“是”学生或研究生“是一类”学生。每个子类型的实例必须也是它的超类型的实例,例如每个研究生概念的实例也是学生概念的实例,如果两个概念间的关系不满足这种条件,那么这种关系不是一般到特殊的关联。

clip_image012

图8-36 一般到特殊关联的例子

一般到特殊的关联允许子类型间存在不同的属性。图8-36中,本科生有一个属性:classLevel,它不属于研究生。共享属性只显示在超类型中,特有属性才出现在子类型中。

一般到特殊的关联的第二个例子是课程与它的班级(如图8-37所示)。课程有编号、标题、单元个数这样的属性,班级具有编号、上课时间、上课地点以及最多学生数目这些属性。运用“……是……”原则,我们发现所有的班级可以被称作是一门课程(课程的教学计划安排),但是每门课程不能被称作班级。

clip_image014

图8-37 一般到特殊关联的另一个例子

课程/班级的例子不能被建模为整体—部分关联。课程并不包含班级,班级也不是课程的一部分。完成了某门课程中一个班级的学习的学生也就完成了这门课程,他或她无须为了获得完整学分而登记同课程的其他班级。

一般到特殊的关联与聚合关联是不同的概念。聚合与实例相关,包含了两个截然不同的实例:一个是另一个的一部分。一般化与概念相关,是为单个实例构建具体描述的一个方法。我们可以通过思考两个问题来区分两种关联:“这个概念的实例是不是也是另一个概念的实例的一部分?”;“这个概念的实例是不是也是另一个概念的实例?”

显示器是计算机的一部分,但是显示器不是一种计算机——因此我们得到了限制的聚合关系(即组合关系)。一个指导老师和一个成员都是属于学生社团的,但是无论是指导老师还是任何一个成员都不是学生社团——还是聚合关系。但是,一个教师是大学的员工,研究生是一类学生——每个都是一般到特殊关联的例子。

  依赖和聚合\组合、关联等有什么不同呢?

    关联是类之间的一种关系,例如老师教学生,老公和老婆,水壶装水等就是一种关系。这种关系是非常明显的,在问题领域中通过分析直接就能得出。

    依赖是一种弱关联,只要一个类用到另一个类,但是和另一个类的关系不是太明显的时候(可以说是“uses”了那个类),就可以把这种关系看成是依赖,依赖也可说是一种偶然的关系,而不是必然的关系,就是“我在某个方法中偶然用到了它,但在现实中我和它并没多大关系”。例如我和锤子,我和锤子本来是没关系的,但在有一次要钉钉子的时候,我用到了它,这就是一种依赖,依赖锤子完成钉钉子这件事情。

    组合是一种整体-部分的关系,在问题域中这种关系很明显,直接分析就可以得出的。例如轮胎是车的一部分,树叶是树的一部分,手脚是身体的一部分这种的关系,非常明显的整体-部分关系。

    上述的几种关系(关联、聚合/组合、依赖)在代码中可能以指针、引用、值等的方式在另一个类中出现,不拘于形式,但在逻辑上他们就有以上的区别。

     这里还要说明一下,所谓的这些关系只是在某个问题域才有效,离开了这个问题域,可能这些关系就不成立了,例如可能在某个问题域中,我是一个木匠,需要拿着锤子去干活,可能整个问题的描述就是我拿着锤子怎么钉桌子,钉椅子,钉柜子;既然整个问题就是描述这个,我和锤子就不仅是偶然的依赖关系了,我和锤子的关系变得非常的紧密,可能就上升为组合关系(让我突然想起武侠小说的剑不离身,剑亡人亡...)。这个例子可能有点荒谬,但也是为了说明一个道理,就是关系和类一样,它们都是在一个问题领域中才成立的,离开了这个问题域,他们可能就不复存在了。

3 高校公修课选课系统的域模型

为高校公修课选课系统建立域模型的方法是每次添加一个事件或一个用例所涉及的概念、属性与关联。

事件1:系提交公修课班级计划列表(用例:提交公修课班级计划列表)

这个事件包括概念系、系公修课班级计划列表、课程、班级、教师以及这些概念间的相互关联。概念系公修课班级计划列表只是一个具有标题的计划表,上面有学期、年份以及班级等信息,系公修课班级计划列表不能离开班级而独立存在,所以把这个关联建模为一个组合。系公修课班级计划列表中也包含了每个班级的上课教师的名字,所以我们必须添加一个教师概念并把它和班级相关联。同时,也要建模课程和班级这个一般到特殊的关联。图8-38为事件1涉及到的部分问题域的域模型。

事件2:到生成学校公修课班级计划列表的时间了(用例:生成学校公修课班级计划列表)

由于学校公修课班级计划列表是由各系公修课班级计划列表构成的,因此不需要添加任何新的概念。

事件3:学生选择班级(用例:选择班级)

选择过程创建了一个学生与班级之间的选择关联的的实例。加入了概念学生和选择关联后,域模型如图8-39所示。

事件4:到生成班级花名册的时间了(用例:生成班级花名册)

班级花名册不包含任何新信息,它可以由现有的域模型生成。

clip_image002[18]

图8-38 事件1相关问题域的域模型

clip_image004[1]

图8-39 高校公修课选课系统的域模型

通过上面的步骤,高校公修课选课系统的域模型就建立起来,也就是建立了分析层次的类图。

1 完整类图的构建

完整的类图中最上面的部分显示类的名称,中间部分包含类的属性,最下面的部分包含类的操作(或者说"方法")。分析层次的类图用于详细说明用户对新系统的需求,没有考虑类的操作。

1.操作

操作是类的对象被要求执行的服务。操作除了要具有名称外,还可具有可见性、参数列表或返回类型。定义操作的基本格式为:

[可见性]操作名[(参数列表)][:返回类型]

可见性分为公有的、受保护的或私有的。操作的可见性为公有的,意味着该操作可由拥有它的对象和其他对象访问;操作的可见性为受保护的,意味着该操作可由拥有它的对象以及该对象所属于的类的子类所产生的对象访问;操作的可见性为私有的,意味着该操作仅仅能由拥有它的对象访问。

操作描述对象的动态特征(行为),可以把操作看作是为其他对象或拥有它的对象所做的工作。对象的操作可分为内部操作和外部操作。内部操作只供对象内部的其他操作使用,不对外提供;外部操作是指,当其他对象用消息请求它时,它进行响应。把一个对象的所有外部操作看作是该对象向外提供的接口,对象通过接口对外部提供服务。在一个对象的内部也可以使用自己的外部操作。

2.完整类图的构建方法

完整类图的构建基于面向对象方法中的域模型、交互图和系统操作约定。根据四个步骤创建一个完整的类图:确定类及其行为;根据域模型中的一般化添加继承;确定关联。

(1)以域模型和交互图为基础,确定软件方案中的类。将出现在交互图中的对象列出一个列表,同时包含它们在交互图中的行为,得到一个初始类图的清单。然后将列表中每个类的一个符号放置在类图中,添加其在域模型中获得的所有属性,类操作出现在每个类矩形的第三个部分中。图8-40展示了一个可能的初始类图。

clip_image006[2]

图8-40 初始类图

clip_image008[2]

图8-41 有关联的类图

(2)向类图中添加继承。域模型的一般化显示了超类中由其子类继承的属性,同样,超类中的操作也由其子类继承。高校公修课选课系统中,班级是课程的特殊化,这意味着课程的所有属性,同时也是每个班级对象的属性。由于这些属性是继承来的,就不需要在班级中进行重新定义。

(3)向类图中添加关联。关联包括聚合、组合以及普通关联。可以根据后面的交互图确定哪些对象发送消息,对每个消息或操作,列出有操作创建的关联并添加到类图中。图8-41展示了一个包含属性、操作和关联的类图。

posted on 2011-04-20 23:28  kingmoon  阅读(5609)  评论(0编辑  收藏  举报

导航