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:
int* pointVar; // preferred syntax
- Assigning Addresses to Pointers
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 codepointVar = &var.
- To get the value pointed by a pointer, we use the
*
operator.
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 ofvar
, we can change the value of by using*pointVar
.
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.
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-aEngine
, 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 theParent
. However, we can still swapchild
ren during the lifetime of theParent
thanks to pointers. - This solution also allows to only forward-declare
class Child
in the header file.
// --- 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 writeclass Parent
’s constructor manually) and theclass Child
is fully declared before the declaration ofclass Parent
.
#include "child.h"
class Parent
{
private:
Child child;
};
Run in Repl.it: https://repl.it/@MaikolGuzman/relationship-01-composition
#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
beforeclass Parent
declaration and thechild
member can be set and re-set during the lifetime of theParent
.
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
child
ren during the lifetime of theParent
object.
class Child;
class Parent
{
public:
Parent(Child& ch) : child(ch) {}
private:
Child& child;
};
Run in Repl.it: https://repl.it/@MaikolGuzman/relationship-02-aggregation
#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.it: https://repl.it/@MaikolGuzman/relationship-03-association
#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.
- A unary relationship connects objects of the same class.
- A binary relationship connects objects of two classes.
- A 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