Composition and Its Implementation
Learn about composition, its importance, and its implementation using a real-life example.
What is composition?
Complex objects are typically constructed by assembling smaller and simpler components. For instance, a car is composed of wheels, doors, an engine, and a frame, while a computer comprises a CPU, a hard disk, a motherboard, and several other components. Similarly, the human body comprises various parts, such as the head, legs, arms, etc. The process of creating a more intricate object by combining simpler ones is referred to as composition.
Object composition can generally be considered to establish a HAS-A or WHOLE-PART relationship between two objects. For instance, a car HAS-A tire(s), a computer HAS-A motherboard, and a human HAS-A leg(s). The complex object involved in the relationship is commonly referred to as the whole or the parent, while the simpler object is often known as the part, child, or component.
Why composition?
Object composition proves to be beneficial as it enables us to construct intricate classes by aggregating simpler, more manageable components. This results in decreased complexity and enables faster and more error-free coding because we can reuse the code of individual composed unit (created, implemented, and tested separately) that has already been tested and verified to function properly.
Real-life example
To exemplify composition relationships, let’s consider the connection between a car and its engine. A car’s engine is an essential part of its body. It can’t function without it. A part in a composition relationship can only belong to one object at a time. Once an engine is installed in a car, it can’t simultaneously power another vehicle.
In a composition relationship, the object controls the existence of its parts. Typically, the part is created with the object’s creation and is destroyed when the object is no longer in use. Therefore, the user does not need to manage the part’s lifetime. For instance, when a car is manufactured, the engine is created and installed within the vehicle. When the car is eventually scrapped, the engine is also scrapped. This aspect of composition is sometimes called a death relationship.
Furthermore, the part is unaware of the whole’s existence. The engine works independently and is unaware of the larger structure in which it is placed. We call this a unidirectional relationship since the car recognizes the engine, but the engine doesn’t recognize the car.
Let’s draw the class diagram:
It is crucial to note that the transferability of parts is not a consideration in composition relationships. For instance, an engine can be removed from one car and placed in another, but once installed in the new car, it becomes a part of that car’s composition.
Implementation of composition in C++
In C++, the composition relationship can be implemented in at least two ways.
-
One approach is to declare an object as a private or public data member and initialize it in the member initialization list of the whole constructor. This involves calling the constructor of the part class’s object, ensuring that the part is created before the whole. This technique is a nice application of member initialization.
-
Another recommended method is to use a pointer-based placeholder and allocate the dynamic object during the constructor or any member function. By using a pointer, we can manage the object’s lifecycle and delete the part when needed to free the memory occupied by the whole. This ensures proper memory management and prevents memory leaks. The choice of implementation depends on the specific requirements of the program and the relationships between the objects.
Example: Engine-car composition relation
Let’s write down the first implementation in C++ according to the example above:
#include <iostream>#include <string>using namespace std;class Engine{string company_name;public:Engine(string n){company_name = n;cout << "An engine of " << company_name << " has been installed." << endl;}~Engine(){cout << "An engine of " << company_name << " has been destroyed." << endl;}};class Car{private:Engine engine;string owner;public:Car(string eng_name, string own_name):engine(eng_name) // member initialization list{owner = own_name;cout << "A car has been created and its owner is: " << endl;}~Car(){cout << "The car has been destroyed which was the ownership of: "<< owner << endl;}};int main(){Car car("NISSAN", "Bob");return 0;}
The given code demonstrates object composition, where the Car
class is composed of an Engine
object.
In the order of creation, the
Engine
object is created first because it is a member of theCar
class. Inside theCar
constructor, theEngine
object is initialized using theeng_name
parameter passed to theCar
constructor. Therefore, when aCar
object is created, anEngine
object is created as well.In the
main()
function, the statementCar car("NISSAN", "Bob");
creates aCar
object namedcar
. This triggers the constructor of theCar
class. Inside theCar
constructor, theEngine
object is created using the providedeng_name
parameter, which, in this case, is"NISSAN"
. So, the output would be:
An engine of NISSAN has been installed.A car has been created and its owner is: Bob
When the program reaches the end of the
main()
function, theCar
objectcar
goes out of scope, and its destructor is called. Inside theCar
destructor, the messageThe car has been destroyed which was the ownership of: Bob
is displayed.Since the
Engine
object is a member of theCar
class, its destructor is automatically called when theCar
object is destroyed. Inside theEngine
destructor, the messageAn engine of NISSAN has been destroyed.
is displayed.Therefore, the destruction order is as follows:
Car
objectcar
(which triggers the destruction of theEngine
object)Engine
object (owned byCar
)
Remember: The order of creation and destruction follows the order of composition, where the composed object (
Engine
) is created before the owner object (Car
) and destroyed after it.
Exercise: Managing university departments with composition
You are tasked with implementing a program to manage departments in a university using object composition. Each department has a name, and the university can have a maximum of five departments. Implement the University
and Department
classes to satisfy the following requirements:
-
The
Department
class should have a constructor that takes a string parameter representing the name of the department. When aDepartment
object is created, it should display a message indicating the creation of the department. TheDepartment
class should also have a destructor that displays a message indicating the destruction of the department. -
The
University
class should have a static (fixed size) array ofDepartment
pointers with a maximum capacity of five. It should also have member variables for the current size of the array and the maximum capacity. Implement a member function calledinsertNewDepartment()
that takes a string parameter representing the name of the department to be created and inserted into the array. TheinsertNewDepartment()
function should dynamically create a newDepartment
object with the provided name and insert it into the array. If the array is already at maximum capacity, it should display a message indicating that the department cannot be inserted. -
The
University
class should have a destructor that deallocates the memory occupied by theDepartment
objects in the array.
Implement the classes and demonstrate their functionality by creating a University
object and inserting departments into it using the insertNewDepartment()
function.
Write a program that demonstrates the functionality of the University
and Department
classes. The program should create a University
object, insert at least three departments into it, and attempt to insert more than the maximum capacity to test the handling of a full array.
Ensure that the program displays appropriate messages when departments are created, inserted, and destroyed.
Example output:
Department "Computer Science" has been created.
Department "Computer Science" has been inserted into the University.
Department "Mathematics" has been created.
Department "Mathematics" has been inserted into the University.
Department "Physics" has been created.
Department "Physics" has been inserted into the University.
Department "Chemistry" has been created.
Cannot insert a new department. University capacity is full.
Department "Computer Science" has been destroyed.
Department "Mathematics" has been destroyed.
Department "Physics" has been destroyed.
Note: Remember to implement the necessary destructors to ensure proper memory deallocation.
Your implementation
Enter your implementation in the following code widget. If you have trouble getting to the solution, you can click the “Show Solution” button to see how to solve the problem.
#include "Department.h"class University{private:static const int MAX_DEPARTMENTS = 5;Department* departments[MAX_DEPARTMENTS];int capacity;int size;public:University() : capacity(MAX_DEPARTMENTS), size(0) {}void insertNewDepartment(string name){// Write your code here}~University(){// Write your code here}};