Class relationships

The relationship is the logical link between objects. How we can distinguish or set up a proper relationship between class objects defines the performance and quality of the system design overall.

Refresh pointers

  • Pointers in C++ can mean the ownership of another (dynamically created) object, or just refer to an object owned by someone else.
  • Pointers are used to store addresses rather than values.
  • The preferred way to declare pointers:
C++
int* pointVar; // preferred syntax
  • Assigning Addresses to Pointers
C++
int* pointVar, var;
var = 5;

// assign address of var to pointVar pointer
pointVar = &var;
  • 5 is assigned to the variable.
  • The address of var is assigned to the  pointer with the code pointVar = &var.
  • To get the value pointed by a pointer, we use the * operator.
C++
int* pointVar, var;
var = 5;

// assign address of var to pointVar
pointVar = &var;

// access value pointed by pointVar
cout << *pointVar << endl;   // Output: 5

In the above code, the address of var is assigned to the pointVar pointer. We have used the *pointVar to get the value stored in that address.
  • When * is used with pointers, it’s called the dereference operator. It operates on a pointer and gives the value pointed by the address stored in the pointer. That is, *pointVar = var.
  • pointVar and *pointVar is completely different.
  • If pointVar points to the address of var, we can change the value of  by using *pointVar.
C++
int var = 5;
int* pointVar;

// assign address of var
pointVar = &var;

// change value at address pointVar
*pointVar = 1;

cout << var << endl; // Output: 1

Aggregation and Composition

  • Describes a whole-part relationship between objects.
  • Aggregation and composition are somewhat similar; they just describe the strength of the connection.

The class that contains an instance or instances of other classes could be instantiated without the aggregate.

A Car can contain a Person object (as a driver or passenger) since they are associated with each other, but the containment is not strong.

C++
class Person; // forward declaration
class Engine { /* code omitted for brevity */ };
class Car {
public:
  Car();
  // ...
private:
  Person* driver; // aggregation
  std::vector<Person*> passengers; // aggregation
  Engine engine; // composition
  // ...
};

The strong containment is expressed by composition. For the Car example, an object of the Engine class is required to make a complete Car object. In this physical representation, the Engine member is automatically created when a Car is created.

Composition

  • Define the composition between the two classes is the has-a relationship test. A  has-a , because a car has an engine. A Car has-a Engine, because a car has an engine.
  • The object is responsible for the existence of the parts, this means that the part is created when the object is created and is destroyed when the object is destroyed, because of this, the composition is sometimes called a “death relation.”
  • The part does not know about the existence of the whole. His heart happily operates without realizing that it is part of a larger structure. We call this a one-way relationship, because the body knows about the heart, but not the other way around.
  • The typical path of embedding private objects of existing classes inside your new class.

Composition with a pointer (not popular)

  • Dynamically create and destroy the child using a pointer.
  • The child’s lifetime is strongly interconnected with the Parent. However, we can still swap children during the lifetime of the Parent thanks to pointers.
  • This solution also allows to only forward-declare class Child in the header file.
C++
// --- header file
class Child;
class Parent
{
public:
    Parent();
    ~Parent();
    void renewChild();
private:
    Child* child;
};
// --- source file
#include "child.h"
Parent::Parent()
    : child(new Child)
{
}
Parent::~Parent()
{
    delete child;
}
void Parent::renewChild()
{
    delete child;
    child = new Child;
}

Composition without pointers (popular)

  • Instead of writing constructor and destructor manually, you can just declare child in the class declaration and let the compiler do the construction and destruction for you.
  • This is valid as long as the class Child’s constructor requires no parameters (otherwise you’d need to write class Parent’s constructor manually) and the class Child is fully declared before the declaration of class Parent.
C++
#include "child.h"
class Parent
{
private:
    Child child;
};

Run in Repl.it: https://repl.it/@MaikolGuzman/relationship-01-composition

C++
#include <string>
#include <ostream>
#include "Punto2D.h"
class Animacion {
    std::string nombre;
    Punto2D posicion;
public:
    Animacion();
    Animacion(const std::string &_nombre, const Punto2D &_posicion);
    virtual ~Animacion();
    void moverseHacia(int _posX, int _posY);
    friend std::ostream &operator<<(std::ostream &os, const Animacion &animacion);
};

Aggregation

The proper question would be can have a; for example, a Car can have a driver (of the Person type); that is, the containment is weak.

Aggregation with a pointer (very popular)

  • Only forward-declare the class Child before class Parent declaration and the child member can be set and re-set during the lifetime of the Parent.
C++
class Child;
class Parent
{
public:
    Parent(Child* ch) : child(ch) {}
    Parent() : child(NULL) {}
    void setChild(Child* ch) { child = ch; }
private:
    Child* child;
};

Aggregation without pointers (not popular)

  • Using a reference. However, this prevents swapping children during the lifetime of the Parent object.
C++
class Child;

class Parent
{
public:
    Parent(Child& ch) : child(ch) {}
private:
    Child& child;
};

Run in Repl.it: https://repl.it/@MaikolGuzman/relationship-02-aggregation

C++
#include "Profesor.h"
class Departamento {
    Profesor *profesor;
public:
    Departamento();
    Departamento(Profesor *_profesor);
};

Association

  • Two components are linked to each other.
  • Referred to as a using relationship, where one class instance uses the other class instance or vice-versa, or both may be using each other.
  • The lifetime of the instances of the two classes are independent of each other and there is no ownership between two classes.
  • Like an aggregation, the associated object can belong to multiple objects simultaneously, and those objects do not manage it.
  • Unlike an aggregation, where the relationship is always one-way, in an association, the relationship can be either one-way or two-way (where the two objects know each other).
  • Because relationships are a broad type of relationship, they can be implemented in many different ways. However, most of the time, associations are implemented using pointers, where the object points to the associated object.

Run in Repl.ithttps://repl.it/@MaikolGuzman/relationship-03-association

C++
#include <string>
#include <vector>
#include <ostream>
#include "Paciente.h"

class Paciente; // Como el doctor y el paciente tienen una dependencia circular, declaramos Paciente

class Doctor {
    std::string nombre;
    std::vector<Paciente *> pacientes;

public:
    Doctor();

    explicit Doctor(const std::string &nombre);

    virtual ~Doctor();

    void agregarPaciente(Paciente *_paciente);

    friend std::ostream &operator<<(std::ostream &os, const Doctor &doctor);

    const std::string &getNombre() const;

    void setNombre(const std::string &nombre);

    const std::vector<Paciente *> &getPacientes() const;

    void setPacientes(const std::vector<Paciente *> &pacientes);
};

Degree of an Association

Degree of an association denotes the number of classes involved in a connection. Degree may be unary, binary, or ternary.

  • unary relationship connects objects of the same class.
  • binary relationship connects objects of two classes.
  • ternary relationship (N-ARY) connects objects of three or more classes.

Cardinality Ratios of Associations

The cardinality of a binary association denotes the number of instances participating in an association. There are three types of cardinality ratios:

  • One–to–One − A single object of class A is associated with a single object of class B.
  • One–to–Many − A single object of class A is associated with many objects of class B.
  • Many–to–Many − An object of class A may be associated with many objects of class B and conversely an object