本文介绍了面向对象的程序设计中类的六大关系,它们分别是Composition、Aggregation、Association、Dependency、Generalization和Realization。

​1. 概述 

在面向对象的程序设计中,类共有六种关系,它们分别是Composition、Aggregation、Association、Dependency、Generalization和Realization。理解类的六大关系对于面向对象的程序设计非常重要,也是理解设计模式的前提。本文给出概念介绍并结合C++代码给出解释,由于作者本身能力有限,难免有不当甚至错误,欢迎指出。文中给出的实例的完整工程详见参考文献1。 

2. 组合(Composition) 

Composition是一种 "part-of" 的关系,如下图中的孕妇和婴儿。它是一种强所属关系,整体与部分往往具有相同的生命周期,整体的对象是在部分对象创建的同时或创建之后创建,在部分对象销毁的同时或之前销毁。

类的关系(C++实现)_设计模式

参考代码如下,讲的是游戏中每个生物体(Creature)都有自己的坐标位置,其中坐标位置用Point类表示,明显是一种"part-of"的关系。

#include <string>

struct Point
{
Point() : x(0), y(0) { }

void setPoint(const int x, const int y)
{
this->x = x;
this->y = y;
}
int getPointX() const
{
return this->x;
}
int getPointY() const
{
return this->y;
}

private:
int x;
int y;
};

struct Creature
{
Creature() : name(""), location(location) { }

void setCreature(const std::string& name, const Point& location)
{
this->name = name;
this->location = location;
}
void move(int x, int y)
{
this->location.setPoint(x, y);
}
std::string getName() const
{
return this->name;
}
int getXLocation() const
{
return this->location.getPointX();
}
int getYLocation() const
{
return this->location.getPointY();
}

private:
std::string name;
Point location;
};

3. 委托(Aggregation) 

Aggregation是一种"has-a"的关系,如图中的Computer和CPU。相对于Composition,Aggregation是一种弱从属的关系,整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。

类的关系(C++实现)_C++_02

参考代码如下,讲的是房子(House)里有一个人(Person),House和Person是可以单独存在的,只不过在目前这种状态下Person在House中,它是一种"has-a"关系(当然此处Person可以是复数,可以采用指针也可以采用引用)。

#include <string>

struct Person
{
Person(const std::string& name) : name(name) { }

const std::string& getName() const
{
return this->name;
}

private:
std::string name;
};

struct House
{
House(const Person& person) : person(person) { }

const std::string getPerson() const
{
return this->person.getName();
}

private:
const Person& person;
};

4. 关联(Association)

Association是一种"use-a"的关系,可以分为双向关联和单向关联。它体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,它使一个类知道另一个类的属性和方法。比如我和我的朋友,老师和学生等都属于关联关系。

类的关系(C++实现)_2d_03

参考代码如下,讲的是医生(Doctor)和病人(Patient)的关系,Doctor给Patient治病,Patient通过Doctor看病,它是一种"use-a"的关系。

#include <functional>
#include <string>
#include <vector>

struct Patient;

struct Doctor
{
Doctor(const std::string& name) : name(name) { }

void addPatient(Patient& patient)
{
this->patient.push_back(patient);
patient.addDoctor(*this);
}

const std::string& getName() const
{
return this->name;
}

std::vector<std::reference_wrapper<const Patient>> patient;

private:
std::string name;
};

struct Patient
{
Patient(const std::string& name) : name(name) { }

const std::string& getName() const
{
return this->name;
}

friend void addPatient(Patient& patient);

void addDoctor(const Doctor& doctor)
{
this->doctor.push_back(doctor);
}

std::vector<std::reference_wrapper<const Doctor>> doctor;

private:
std::string name;
};

代码中的std::reference_wrapper详见参考文献2,此处不能使用std::vector<const Doctor&>,因为引用只能在构造函数中被初始化且不能被重新赋值。一个可行的方案是采用std::vector<const Doctor*>,但是要注意nullptr和内存的正确释放,这真的让人头痛。std::reference_wrapper提供给我们一个新思路,其实它和引用特别像,但是它允许被重新赋值和拷贝。 

5. 依赖(Dependency) 

Dependency是一种"depends-on"的关系,它比Association的关系若。它强调的是一种单向的关系,一个类的实现需要另一个类的协助。如某人要过河,人此时就会"depends-on"船。

类的关系(C++实现)_C++_04

参考代码如下,讲的是Point2D对象的打印输出,它"depends-on" std::ostream。

#include <iostream>

struct Point2D
{
Point2D(int x, int y) : x(x), y(y) { }

int getX() const
{
return this->x;
}
int getY() const
{
return this->y;
}

friend std::ostream& operator<<(std::ostream& os, const Point2D& point)
{
os << "Point(" << point.x << ", " << point.y <<")";
return os;
}

private:
int x;
int y;
};

6. 继承(Generalization) 

对于public的Generalization来说,它是一种"is-a"的关系;对于private的Generalization来说,个人理解只是实现上避免重复代码的一种手法而已,它是一种"is-implemented-in-terms-of"的关系(此处在参考文献3 Effective C++的39条《审慎的使用private继承》有详细介绍);对于protected的继承来说,个人没有理解,可能仅仅为了统一形式吧。

类的关系(C++实现)_设计模式_05

参考代码如下,讲的是学生(Student)是一个人(Person),明显是一种"is-a"的关系。

#include <string>

struct Person
{
Person(const std::string& name) : name(name) { }

const std::string& getName() const
{
return this->name;
}

private:
std::string name;
};

struct Student : Person
{
Student(const std::string& name, const int number)
: Person(name), number(number) { }

const int getNumber() const
{
return this->number;
}

private:
int number;
};

7. 实现(Realization) 

Realization是一种面向对象中接口和类之间的"realization"关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的操作。在C++中,接口通过的纯虚函数来实现,C++的多态就是通过虚函数来实现的。

类的关系(C++实现)_软件开发_06

参考代码如下,讲的是Dog和Bird都是Animal,但是它们的move()操作各不相同,通过在基类(Animal)中声明接口和在Dog、Bird类中去"realization"接口。

#include <string>

struct Animal
{
virtual ~Animal() {}

virtual std::string move() const = 0;
};

struct Dog : Animal
{
std::string move() const override
{
return "run";
}
};

struct Bird : Animal
{
std::string move() const override
{
return "fly";
}
};

8. 总结 

本文分享了类的六大关系,分析了各自的特点,并给出实例方便理解。本文所要强调的是要立足需求,根据抽象后的具体关系选用合适的"Relationship"去实现,而不是上来就是public继承("is-a"的关系)。当然现实中不可能只是简单的六种关系中的某一种,更多的是多个关系的组合。相信对类的关系有了正确的认识后,再去理解设计模式就比较得心应手了。 

9. 参考文献

1.https://github.com/mzh19940817/ClassRelationship

2.C++11中std::reference_wrapper的理解,https://blog.csdn.net/guoxiaojie_415/article/details/80031948

3.Scott Meyers, Effective C++

本文结合实例分享了类的关系,文中可能有些许不当及错误之处,代码也没有用做到尽善尽美,欢迎大家批评指正,同时也欢迎大家评论、转载(请注明源出处),谢谢!