Association, Composition and Aggregation in C++

In this tutorial from our C++ course, we will learn how different Classes and Objects relate to each other, and study this relationship further.

Classes and Objects in Real Life

So far, in our course, we have used classes in a very isolated way, creating and using only one class. But in the real world, it rarely happens. We live in a world of objects.

Often, it only makes sense that there is an object, if there are others. For example, we can have the Human class and the Food class. Human-type objects are you, me, our friends, family ... Food-type objects, rice, beans, meat ... does it make sense for Human-class objects to exist without Food-class objects? Of course not.

And even if one object does not depend on another to exist, it can depend on others to 'work'. For example, objects from the Engine, Gear, Brake, etc. classes work together to make a Car type object work.

That is, in the real world, objects are not isolated. In your projects, you will always create several classes, instantiate several objects, make one create another, send information, another receive data from another object, the method of an object using several other objects, and so on.

Association, Composition and Aggregation

When an object uses a method or service from another object, we say that there is an association there. Yes, it is very broad and generic. We will specify more the types of relationship, in composition and aggregation.

In both cases, an object has another object. However, in composition, an object exists only if the other exists.

For example, the object of type Car only exists if it has an object of the Engine class. There is no car without an engine.

A Human type object, only exists if it has objects like Heart, Lung ... there is no person without these organs (at least for now, some program will design and create organs in the future - of course, it would use C++).

In aggregation, an object has another, but it could exist without it. For example, every car has an owner. That is, it has Human-type objects that have Car-type objects. But a human being does not necessarily need a car to exist.

You need a heart, a head ... mandatorily. Not a car. The relationship of a Car-type object and a Human-type object is aggregation.

Association, Composition and Aggregation in C++

In composition, when a parent object is destroyed, the child objects will also be destroyed. One does not exist without the other. In aggregation, no. In other words, association is something very generic, then there is aggregation (one can exist without the other) and finally the composition, the most specific relationship, where an object only exists if another exists.

But let's put these generic conversations aside and see in practice some relationships between objects.

Composition in C++

Let's start with the most specific. To make it easier, we call the Human and Car classes parent classes. The classes Engine, Heart, Gear, Lung etc., are the child classes, members or components. Or we call it All and Parts, or Object and Member.

To be characterized as a composition, a member is part of the whole. Each part belongs to only one object (that is, the heart belongs only to a human, and the car's engine belongs to only one car).

The class that represents the Whole, will manage the parts, the components. These members are unaware of the existence of the 'Whole'.

Let's create the Engine class, in the constructor we warn that we are starting the engine and in the destructor function we warn that we are turning the engine off. Engine.h:

#ifndef ENGINE_H
#define ENGINE_H

class Engine
{
    public:
        Engine();
        ~Engine();
};

#endif // ENGINE_H

Engine.cpp

#include "Engine.h"
#include <iostream>
using namespace std;

Engine::Engine()
{
    cout << "Starting the engine..."<<endl;
}

Engine::~Engine()
{
    cout << "Turning off the engine..."<<endl;
}

Now we are going to create the Car class, which will do almost the same as the Engine, it will say 'starting the car' in the constructor and 'turning the car off' in the destructor. However, it will have a member, a pointer for the Engine type, the variable 'myEng'.

In the class, we just declare this pointer, Engine.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        Car();
        ~Car();
        Engine *myEng;
};

#endif // CAR_H

But in the implementation, we instantiate this pointer, causing the constructor method of the 'myEng' object to be invoked. In Car's destructor function, we delete the Engine pointer, automatically invoking the destructor function, Car.pp:

#include "Car.h"
#include <iostream>
using namespace std;

Car::Car()
{
    cout << "Let's start the car."<<endl;
    myEng = new Engine();
}

Car::~Car()
{
    delete myEng;
    cout << "Car off."<<endl;
}

Our main.cpp:

#include <iostream>
#include "Car.h"
using namespace std;

int main()
{
    Car myCar;

    return 0;
}

The result is as follows:

Let's start the car.
Starting the engine...
Turning off the engine...
Car off.

The 'myEng' object EXISTS ONLY because of the 'myCar' object. Without 'myCar', there is not 'myEng'. 'myEng' belongs to 'myCar'. One object belongs to another.

Whoever 'controls' myEng is also myCar. He who creates the object, instantiates ... and the Car class object dies, the Engine class object also dies. The 'myEng' object belongs only to the 'myCar' object, to none other. And he doesn't know about any other object either, and he doesn't know anything about the Car class.

Those things that characterize the composition well.

Aggregation in C++

We are now going to create the Human class, to create people. They will drive the car. To create an object of this class, we need to pass a string with the person's name and an object of type Car, see how Human.h looks:

#ifndef HUMAN_H
#define HUMAN_H
#include <string>
#include "Car.h"

class Human
{
    public:
        std::string name{};
        Car myCar;
        Human(const std::string& str, Car car);
        ~Human();

};

#endif // HUMAN_H

Now the Human.cpp implementation:

#include "Human.h"
#include <iostream>
#include <string>
using namespace std;

Human::Human(const std::string& str, Car car)
{
    name = str;
    myCar = car;
    cout<<name;
    myCar.turnOn();
}

Human::~Human()
{
    cout<<name;
    myCar.turnOff();
}

We made some changes to Car.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        void turnOn();
        void turnOff();
        Engine *myEng;
};

#endif // CAR_H

And in the Car.cpp implementation:

#include "Car.h"
#include <iostream>
using namespace std;

void Car::liga()
{
    cout << " will turn on the car."<<endl;
    myEng = new Engine();
}

void Car::desliga()
{
    cout <<" turned off the car."<<endl;
    delete myEng;
}
 

That is, now we have the functions turnOn() and turnOff(), on objects of the Car type (before we used to turn on and off the constructor and destructor).

See how our main.cpp looks like:

#include <iostream>
#include "Car.h"
#include "Human.h"

using namespace std;

int main()
{
    Car car;
    Human *h1 = new Human("Neil Peart", car);
    delete h1;

    cout << endl;

    Human *h2 = new Human("Bruce Dickinson", car);
    delete h2;

    return 0;
}

That is, we created an object of the Car class and two of the Human class.
Let's see some interesting things. The result if we run the code above is:

Neil Peart will start the car.
Engine running.
Neil Peart turned off the car.
Engine off.

Bruce Dickinson will start the car.
Engine running.
Bruce Dickinson turned off the car.
Engine off.

First, the same 'car' object is used for both 'h1' and 'h2' objects.

Second, it is the h1 and h2 objects that trigger the car's turnOn() and turnOff() functions, that is, they handle the 'car' object.

Finally, the 'car' object does not cease to exist when the 'h1' object ceases to exist. It remained there alive, so much so that it was used by the object 'h2' shortly thereafter. That is, although there is a relationship between them (some objects use others), they are somewhat independent, unlike the relationship between Car and Engine, where engines ONLY EXIST as long as there are cars, and only one engine is used for each car .

These are the characteristics of aggregation.
Between Car and Engine, it's composition.
Between Human and Car, it's aggregation.

Association in C++

Let me tell you the biggest secret of the C++ language: a large, complex system, like a space station or Microsoft Excel, is nothing less than several small objects, playing specific roles.

In other words, a complex system is nothing more than several small objects, working together, as an association.

Thus, during your career as a C++ programmer, always try to create specific, well-defined Classes and Objects, with the right functions and as simple and direct as possible.

To repeat: a large project or system is nothing more than a looot of objects working together, in association, whether by composition or aggregation.

Organization is the key to success in working with object-oriented programming!

No comments:

Post a Comment