什么是泛化关系?用一个例子简单的说:假设A是B和C的父类,B、C具有公共类(父类)A,说明A是B、C的一般化(概括,也称泛化),B、C是A的特殊化。
在编程上,泛化关系(Generalization)就是我们常说的继承关系,称为“is-a-kind-of”关系,泛化关系用于描述父类与子类之间的关系,父类又称作基类或超类,子类又称作派生类。在UML中,泛化关系用带空心三角形的直线来表示。
在代码实现时,使用面向对象的继承机制来实现泛化关系,如在Java语言中使用extends关键字、在C++/C#/OC中使用冒号“:”来实现。
UML示例图如下所示:
在UML当中,对泛化关系有三个要求:
- 子类与父类应该完全一致,父类所具有的属性、操作,子类应该都有;
- 子类中除了与父类一致的信息以外,还包括额外的信息;
- 可以使用父类的实例的地方,也可以使用子类的实例。
实现关系是用来描述接口和实现接口的类或者构建结构之间的关系,接口是操作的集合,而这些操作就用于规定类或者构建结构的一种服务。
在接口和类之间的实现关系中,类实现了接口,类中的操作实现了接口中所声明的操作。在UML中,类与接口之间的实现关系用带空心三角形的虚线来表示。UML示例图如下所示:
在UML中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方。UML示例图如下所示:
示例代码如下(People.m):
#import "People.h"
@implementation People
- (void)eat:(Food *)food {
NSLog(@"I am eating food.");
}
- (void)read:(Book *)book {
NSLog(@"I am reading.");
}
@end
4、 关系(Association)
在UML类图中,用实线连接有关联的对象所对应的类,在使用Java、C#和C++等编程语言实现关联关系时,通常将一个类的对象作为另一个类的属性。
1.双向关联。
默认情况下,关联是双向的,双向的关联可以有两个箭头或者没有箭头。
2.单向关联。
类的关联关系也可以是单向的,单向关联用带箭头的实线表示。
单向关联和双向关联的UML示例图如下所示::
【说明】:上图中,Teacher与Student是双向关联,Teacher有多名Student,Student也可能有多名Teacher(两个类连线下面的*表示多对多的关系)。但Student与Course间的关系为单向关联,一名Student可能有多门Course,课程是个抽象的东西,因此不拥有Course。单向关联和双向关联的示例代码如下(Teacher、Student类的定义):
Teacher.h文件:
@class Student;
@interface Teacher : NSObject {
Student *_student;
}
@property (nonatomic, retain) Student *student;
@end
Student.h文件:
#import "Course.h"
@class Teacher;
@interface Student : NSObject{
Teacher *_teacher;
Course *_course;
}
@property (nonatomic, retain) Teacher *teacher;
@property (nonatomic, retain) Course *course;
@end
【注意】:可能大家注意到了,在Student类的定义中,包含Course类用的是:#import "Course.h";而包含Teacher类用的是:@class Teacher;。这是因为Teacher类和Student是双向关联,如果直接用import,那么在编译的时候,编译器会报错,至于@class和#import的区别,在后面会进行介绍。
3.自关联。
在系统中可能会存在一些类的属性对象类型为该类本身,这种特殊的关联关系称为自关联。比如我们在数据结构中描述树结构,会建一个节点Node类,Node类有一个指针父节点也是Node类型。
5、 聚合(Aggregation)聚合关系是关联关系的一种特例,它体现的是整体与部分的关系,即has-a的关系,此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。比如计算机与CPU、公司与员工的关系等。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。
在聚合关系中,成员类是整体类的一部分,即成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用带空心菱形的直线表示。UML示例图如下所示:
示例代码如下(概要,完整源码见附件):Computer.m文件
#import "Computer.h"
@implementation Computer
@synthesize centralProcessingUnit = _centralProcessingUnit;
- (id)initWithCpu:(CentralProcessingUnit *)cpu{
self = [super init];
if (self != nil){
self.centralProcessingUnit = cpu;
}
return self;
}
- (void)dealloc{
[_centralProcessingUnit release];
NSLog(@"Computer dealloc");
[super dealloc];
}
@end
调用代码:
CentralProcessingUnit *centralProcessingUnit = [[CentralProcessingUnit alloc] init];
Computer *computer = [[Computer alloc] initWithCpu:centralProcessingUnit];
// computer生命周期结束
[computer release];
// centralProcessingUnit还可以进行其他操作.....
[centralProcessingUnit release];
从调用代码我们可以看到,我们创建了一个独立的
centralProcessingUnit
对象,然后将这个对象传入了
Computer
的
init
函数。当
computer
对象生命周期结束的时候,
centralProcessingUnit
对象如果还有其他指向它的引用,是可以继续存在的。也就是说,它们的生命周期是相对独立的。
6、 组合(Composition)
组合也是关联关系的一种特例,它体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;它同样体现整体与部分间的关系,但此时整体与部分是不可分的,它们具有统一的生存期,整体的生命周期结束也就意味着部分的生命周期结束,部分对象与整体对象之间具有同生共死的关系,组合关系中的部分,是不能在整体之间进行共享的。比如人和眼睛,当然,有人会说现在医学发达,眼睛可以移植给别人,如果是这样的话,你可以理解人和眼睛的关系为聚合,这都是在具体的场景下来确定的。表现在代码层面,和关联关系是一致的,只能从语义级别来区分。在组合关系中,成员类是整体类的一部分,而且整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。 在UML中,组合关系用带实心菱形的直线表示。UML示例图如下所示:
示例代码如下(完整代码见附件):
People.m文件:
#import "People.h"
@implementation People
@synthesize eye = _eye;
- (id)init{
self = [super init];
if (self != nil){
_eye = [[Eye alloc] init];
}
return self;
}
- (void)dealloc{
[_eye release];
NSLog(@"People dealloc");
[super dealloc];
}
@end
从上面我们可以看到,
Eye
对象是在
People
对象里面创建的,所以在
People
对象生命周期结束的时候,
Eye
对象的生命周期也同样结束了。

对于继承、实现这两种关系没多少疑问,它们体现的是一种类与类、或者类与接口间的纵向关系;其他的四者关系则体现的是类与类、或者类与接口间的引用、横向关系,是比较难区分的,有很多事物间的关系要想准确定位是很难的,前面也提到,这几种关系都是语义级别的,所以从代码层面并不能完全区分各种关系;但总的来说,后几种关系所表现的强弱程度依次为:组合>聚合>关联>依赖。