类的关系(C++实现)

1. 概述

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

2. 组合(Composition)

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


Composition.jpg

参考代码如下,讲的是游戏中每个生物体(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是一种弱从属的关系,整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。


Aggregation.jpg

参考代码如下,讲的是房子(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"的关系,可以分为双向关联和单向关联。它体现的是两个类、或者类与接口之间语义级别的一种强依赖关系,它使一个类知道另一个类的属性和方法。比如我和我的朋友,老师和学生等都属于关联关系。


Association.png

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

5. 依赖(Dependency)

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


Dependency.jpg

参考代码如下,讲的是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的继承来说,个人没有理解,可能仅仅为了统一形式吧。


Generalization.jpg

参考代码如下,讲的是学生(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++的多态就是通过虚函数来实现的。


Realization.jpg

参考代码如下,讲的是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++

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容