类的关系(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++

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容