在面向对象的程序设计中,类之间有6中关系,分别是继承, 组合, 聚合,关联, 依赖,实现。
组合(Composition)
组合是将一个对象(部分)放到另一个对象里(组合)。它是一种 “has-a” 的关系。相比"聚合",组合是一种强所属关系,组合关系的两个对象往往具有相同的生命周期,被组合的对象是在组合对象创建的同时或者创建之后创建,在组合对象销毁之前销毁。一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象。比如,鸟类和翅膀类就是组合关系,在创建一个鸟类对象时,一定要同时或之后创建一个翅膀类对象,销毁一个鸟类对象时,一定要先同时或之前销毁翅膀对象。
部分和组合需要满足以下关系:
- 该部分(成员)是对象(类)的一部分
- 零件(成员)一次只能属于一个对象(类)
- 该部分(成员)的存在由对象(类)管理
- 部分(成员)不知道对象(类)的存在
简单说,一个类中有若干数据成员是其他类的对象。以前我们看到的类的数据成员都是基本数据类型的或自定义数据类型的,比如int、float类型的或结构体类型的,现在我们知道了,数据成员也可以是类类型的。
如果在一个类中内嵌了其他类的对象,那么创建这个类的对象时,其中的内嵌对象也会被自动创建。因为内嵌对象是组合类的对象的一部分,所以在构造组合类的对象时不但要对基本数据类型的成员进行初始化,还要对内嵌对象成员进行初始化。
一个好的经验法则是,应该构建每个类来完成一项任务。该任务要么是某种数据的存储和操作,要么是子类的协调。
在C++语法中,使用在一个类中包含另外一个类类型的成员来实现组合。
class Wing{
};
class Bird{
Wing wing;
};
聚合(Aggregation)
聚合是一种弱所属关系,比如一只大雁和雁群,就是一种"聚合"关系。和组合相比,被聚合的对象可以属于多个聚合对象,比如,一只大雁可能属于多个雁群。
为了符合聚合条件,整个对象及其各个部分必须具有以下关系:
- 该部分(成员)是对象(类)的一部分
- 零件(成员)一次可以属于多个对象(类)
- 部件(成员)的存在不由对象(类)管理
- 部分(成员)不知道对象(类)的存在
在聚合中,我们还将零件添加为成员变量。但是,这些成员变量通常是引用或指针,用于指向已在类范围之外创建的对象。因此,聚集通常要么将要指向的对象用作构造函数参数,要么开始为空,然后通过访问函数或运算符添加子对象。
因为这些部分存在于类范围之外,所以当销毁该类时,指针或引用成员变量将被销毁(但不会删除)。因此,零件本身仍将存在。
在C++语法中,通过类的指针来实现聚合
#include <string>
#include <iostream>
class Teacher
{
private:
std::string m_name;
public:
Teacher(std::string name)
: m_name(name)
{
}
std::string getName() { return m_name; }
};
class Department
{
private:
Teacher *m_teacher;
public:
Department(Teacher *teacher = nullptr)
: m_teacher(teacher)
{
}
};
int main()
{
Teacher *teacher = new Teacher("Bob"); // create a teacher
{
Department dept(teacher);
} // dept goes out of scope here and is destroyed
std::cout << teacher->getName() << " still exists!";
delete teacher;
return 0;
}
在这种情况下,教师是独立于部门创建的,然后传递给部门的构造函数。销毁dept时,m_teacher指针也将销毁,但教师本身不会被删除,因此它仍然存在,直到以后在main()中将其独立销毁为止。
关联(Association)
关联也是一种弱关系,但并不是从属关系,关联的连个的类可以看作是平等的,比如一只大雁和老鹰的关系,就可以看作关联关系
C++中,通过定义其他类指针类型的成员来实现关联,下面是双向关联的实现方法
class Egle{
class Goose *food;
};
class Goose{
class Egle *predator;
};
关联、聚合、组合之间的对比
依赖(Dependency)
一个对象的某种行为依赖于另一个类,比如,大雁的迁徙行为受季节影响,那么,大雁和季节就会有"依赖"关系。
C++语法中,通过将一个类作为另一类方法的参数的形式实现两个类之间的依赖关系
class Season{
};
class Goose{
public:
void Migrate(Season season); //或Migrate(Season *season)、Migrate(Season &season)
};
继承和实现是后面要重点介绍的内容
继承(Generalization)
继承是面向对象的三大特征之一,是一种最能体现面向对象代码复用的类关系,对于继承,可以使用"is a"来表示,比如,小轿车(类B)"is a"车(类A),是对车(类A)的进一步刻画,那么这两个类就是"继承"关系。
class Goose : public Bird{
//子类扩展属性和方法
};
实现(Realization)
实现对应的是面向对象中的"接口",即动物都要移动,但是每种移动的方式不一样,鸟要实现自己独有的移动的方法。
在C++中,接口通过的纯虚函数来实现,C++的多态就是通过虚函数来实现的。
class Animal{
public:
vitual void move();
};
class Bird: public Animal{
void move(){
//鸟的移动方式,飞
}
};