OOP和UML
文/Alex Marbus
一. 引言
●简介
这篇文章的目的是提供给你有关UML的信息和怎样使用它。
●什么是UML?
UML,统一建模语言(Unified Modeling Language),是一个适合于真实世界的对象模型的标准符号,在面向对象程序设计中的开发中被视为是第一步。它为指定,肉眼观察,构造和记录软件系统的人造物品描述了一种统一的语言。
●为什么做成模型?
在你真正开始设计软件之前为一个软件系统开发一个模型,可以被视作为你的工程设计了一个蓝图。这是基本的工作,尽管这并不意味着你每次都要描绘一个模型,或者在你的软件中引进一个简单的类。
二. 一些符号规则
●图形和它们的内容
多数UML图表是包含了通过路径接点连接的图形。它大部分是类型学的信息,而不是依尺寸或者符号的位置。下面是三种可见的重要方法的种类:
1. 连接(通常是线)
2. 夹角(沿边界的2D形状)
3. 可见的附属物(一个对象挨着另一个对象)
UML符号往往是在一个2D平面上画出来的。 在UML符号中有四种基本图形结构:
1. 图标——图标是一个固定大小和形状的图形轮廓。它不会扩大为控制内容。图标可能会在符号区域里作为一个路径边界,要么作为一个可能或者不可能连接到路径的单独的符号出现。
2. 2D符号——两个空间符号有易变的长度和宽度,它们可以扩大为控制其他的东西,如列表,字符串或者其他符号。它们多数被分成相同或者不同种类的字格。拖动或者删除一个2D符号都会影响它的内容和任何与它相连的路径。
3. 路径——具有端点的线段的序列。从概念上说,路径是一个单独的拓扑实体,尽管它的段可以巧妙地处理图形。段不能远离它的路径单独存在。路径总是被放在其他图形符号的两个端点上(不能是摆线)。路径也许有边界,就是在路径末端的一些序列中出现的图象,这限制了路径符号的内涵。
4. 字符串——现在各样的信息都在一个“未解析”的表格中。UML假设符号中字符串的每个用法都有一个语法,通过这个语法字符串就能被解析到下面的模型信息中。例如,语法是与属性,操作和转变作交换的。字符串可能是作为单独的符号元素或者符号组件存在的,像列表中的元素作为标签附加给符号或者路径,或者作为图表中单独的元素。
三. 关系
● 属性和行为
每个对象都有不同的属性。属性就是一对名字/值。例如,我的年龄是22。在属性中,名字是“AGE”,值是22。对象也可以有行为。我可以站,走,睡,等等。
下面是不同种类的关系:
“依赖性”是指一个对象必须知道另一个对象在哪儿。
在一个简单的依赖关系中,所有我们知道的是一个对象有另一个对象的信息。例如:如果一个类要求包含另一个类的头文件,那就确立了一个依赖关系。我们使用一个虚线箭头描述一个类。如果A依赖B,那么使箭头指向B。
在“联合”里,对象的状态依赖于另一个对象。在一个联合里我们可以说,作为理解一个对象状态的一部分,你必须理解与第二个对象的关系。模拟真实世界关系的联合有很多类型,像“拥有(Arjen拥有这辆自行车)”,“(Raymond为Harry工作)”等等。
在一个联合里,两个对象必须有很强的联系,但是没有一个是另一个的一部分。关系强于依赖;一个联合影响双边关系。
A和B的联合通过连接两个类的线表现出来:
如果线上没有箭头,联合就求助于双边的方向。一个单向性的联合像这样表示:
为使类图表的表达更清楚,两个对象间的联合可以这样指定:
“集合”模拟整体/部分的关系。
对象经常由另一个对象构成。汽车是由可操纵的轮子,发动机,传送带等等构成的。每个组件从自己的角度看都是一个对象。一个汽车对它的组件部分的特殊联合就是我们知道的“集合”。将一个白色的菱形放在挨着集合类的联合的末端上就表明了集合的关系。如果B集合A,那么A是B的一部分,但是它们的寿命是独立的。
“合成”模拟了一个对象是另一个对象的主要部分的关系。
通常,一个对象的组件部分仅仅用整个对象融入实体。例如,一个人由许多部分组成,包括心脏,肺脏等等。如果你为一个人建模,心脏和肺的寿命将直接由合成人的寿命决定。我们称这种特别的关系为“合成”。
在集合中,集合的组件部分可以独立的生存。虽然我的汽车由它的轮子、轮胎和收音机组成,但它们也可以在我的汽车生产出来之前继续存在。在合成中,被包含的对象的寿命与包含这些对象的对象的寿命是紧密联系的。
合成可以用一个黑色的菱形放在挨着合成类的联合的末端上来表示。如果B由A组成,那么B控制A的寿命。
四. 继承
●继承
“继承”在对象中是一个特殊化/一般化的关系。我们(人类)继承了在我们的环境中通过事物的行为和特征辨别种类的能力。这是最能表达的例子:如果某物呼吸和运动,我们说它是一只动物。如果一个东西运动,呼吸,也有活着的幼仔,还照料它们,我们说它是一只哺乳动物。我们知道哺乳动物是动物的一种,所以我们能预知如果我们看见一只动物,它将会在周围呼吸和运动。
如果一只哺乳动物咆哮着摇动尾巴,我们说它是一只狗。一些哺乳动物是狗,一些是马。每种类型的动物都有确定的特征,这帮助我们识别它们,预知它们的行为和特征。
这只有一种合适的方式来描述:
一旦我们有了这个分类,我们可以看到熟读动物层次揭露了共有特征的“一般化”。
用同一种方式,我们可以创建一个汽车模型。要做这些事情,我们必须问自己一些问题:
汽车是什么?是什么使汽车不同于卡车,不同于一个人,不同于一个岩石?面向对象程序设计的乐趣之一是这些事情变的与我们有关。明白我们怎样在真实世界中认识和考虑对象与我们怎样在模型中建立这些对象直接有关。
在一个观点上,汽车是组件部分的总和:方向盘,刹车,座位,前灯。这里,我们根据集合来思考。在又一个观点上,也就是真正等价地,汽车是车辆的一种。
因为汽车是车辆,它搬家和搬送货物。那是车辆的基本的事情。汽车从它们的父类“车辆”中继承了特征“搬家”和“搬送货物”。
我们也知道汽车用于车辆。它们是车辆的一个特殊类别,也满足汽车的联合规范。
我们可以用继承模拟这个关系。我们说汽车类型公开地从车辆继承;一辆汽车是一个车辆。
公共继承确立了一个“是一个”的关系。它建立了一个父类(车辆)和一个子类(汽车),这暗示了汽车是一种典型车辆的特殊化。有关车辆的一切真实的事情对汽车也应该是真实的,但是相反的事物却不会是真实的。汽车也许用于它“怎样”运动,但是它应该运动。
机动车辆是什么?从一个抽象的层面上看这是一种不一样的特殊化情况。机动车辆是一种发动机驱动的车辆。一辆汽车是一种类型,一辆卡车又是另一种类型。我们最好还是用继承模拟这种复杂的关系。
哪一种模型较好取决于你建的是什么模型。你怎样决定你应该使用哪种模型呢?问你自己吧!有我想建模的“机动车辆”的一些东西吗?我要建立另一个模型,非激动化的车辆吗?如果你想,你应该使用另一个模型。可以用一个例子表示:假设你想为马拉的车辆建立两个类会怎样?
● 公共继承
“公共继承”一个危急的方面是它要模拟特殊化/一般化,并没有别的。如果你想继承执行,但是并不确定一个关系,你应该使用“私有继承”。
私有继承确立了一个“有根据地执行“的关系胜于“是一个”关系。
● 多重继承
C++中有用的性能之一是“多重继承”。多重继承允许一个类从不止一个的基类继承,引入两个或更多的类的成员和方法。
在简单的多重继承中,两个基类是无关的。下面是多重继承的一个例子。注意在这个模型中函数是怎样显示的。
在这个相当简单的模型中,Griffin类从Lion和Eagle继承。这意味着Griffin可以eatMeat(),roar(),squawk()和fly()。当Lion和Eagle共享一个共同的基类时,问题产生了,例如Animal。
这个共同的基类Animal可以有Griffin现在继承了两次的成员变量的方法。当你呼叫Griffin的Sleep()方法,编译器将不知道你想调用哪个Sleep()。作为Griffin类的设计者,你必须明白它们的关系和准备好解决它们创建时的不明确。C++通过提供了“虚拟继承”,使这个问题更容易解决。
用虚拟继承,Griffin只继承了Animal成员的一个拷贝,这个不明确解决了。问题是Lion和Eagle两个类必须知道它们可能被牵涉进多重继承关系中。关键字“虚拟”必须在它们的继承声明中,而不是在Griffin中。
五. 集合
● 在你需要集合时使用多重继承。
你怎么知道什么时候使用多重继承和什么时候回避呢?一辆汽车应该从方向盘,轮胎和门继承吗?一辆警车应该从市政财产和车辆继承吗?
第一个方针是公共继承应该总是模拟特殊化。这个的共同表达是继承应该模拟“是一个”关系,集合应该模拟“有一个”关系。
汽车是一个方向盘吗?很显然不是。你必须弄清楚汽车是方向盘,轮子和门的联合,但是这并不模拟继承。一辆汽车不是这些东西的特殊化;它是这些东西的集合。一辆汽车有一个方向盘,它有门,它有轮胎。另一个为什么不从门继承汽车的很好的理由是一辆车通常不止有一扇门的事实。这不是用继承可以模拟的关系。
一辆警车是一个车辆和市政财产吗?显然都是。事实上,它属于这两个。同样地,多重继承在这里产生了很多意义:
● 基类和子类
子类应该知道谁是他们的基类。它们依赖于基类。另一方面,基类对于它们的子类则什么也不应该知道。别把字类的头放进你的基类文件。
你应该非常怀疑任何“推翻”继承层次呼叫的构思。当你为它的“真实”(运行时间)类询问一个指针时你会很沮丧,然后计算指向子类型的指针。理论上,基指针应该是多形态的,计算出指针的“真实”类型和呼叫“正确”的方法应该留给编译器。
推翻大多数共同的用途是调用一个在基类中不存在的方法。你应该问你自己的问题是为什么你在你需要这样做的位置。如果运行时间类的知识是隐藏的,那么你为什么沮丧?
● 单一实例类
你也应该知道哪一个子类总是只有一个实例。不要与一个单独实例——程序只需要一个类的单一的实例——搞混,例如只一个文档或一个数据库。
六. 描述一个类
● 显示成员
假定你想创建一个类CfloatPoint,它有两个成员:x和y,类型都是‘float’,还有一个重置两个成员,赋值为0.00000的函数Empty()。
首先,你描述类本身:
现在,我们使成员x和y在模型中可见:
如我们所见,x和y都的是私有的(用加锁表示),还有类型“float”。
现在,我们想使函数Empty()在模型中可见:
● 笔记
假如你想给你的类加一些附加信息。你可以很容易地添加一个笔记,像这样:
七. 结论
● 使用的软件
Visual modeler
紧记这些模型是用Visual Studio Enterprise Edition 发布的Visual modeler创建的。所以你可以自己试着描述它们。这里我不会解释Visual Modeler怎样工作的。更多信息请参见手册或者MSDN Library。
● 这个文件将来的版本
我想这篇文章是OOP和UML一个很好的入门。加之文档“Different styles of Programming ”提供了一个很好的OOP(Object Oriented Programming)指南。
在这篇文章类有许多公开的UML的特写。它们之一是小说中才有的所谓的“使用案例”。这将在以后写出的新文档作出解释.