# Composition

By using composition one builds objects that consist of other objects. Think of it as creating a new type of object by packaging together other objects.

Composition also allows one to hide complexity behind the simplicity of objects. In other words, objects allow one to create new levels of abstraction.

Composition comes with a great deal of flexibility. Member objects of the new class can be made private, making them inaccessible to client programmers. This means that there can be changes without disturbing existing client code. This can even be done at runtime, to dynamically change the behavior of your program. This cannot be done using inheritance since the compiler must place compile-time restrictions on classes created with inheritance.

Because inheritance is one of the main pillars of object oriented design, it is often over-estimated and over-used. When used wrongly, it can result in awkward and overly-complicated designs. A good practice is to look at composition first when creating new classes as it is simpler and more flexible.

# Association, Composition and Aggregation

The simplest way to use a class is by creating objects from it and using those objects. In other words an object of one class may use services/methods provided by an object of another class. This kind of relationship is termed as an association.

An association represents a relationship between two or more objects where all objects have their own lifecycle and there is no owner. The name of an association specifies the nature of relationship between objects. This is represented in UML by a solid line.

Hangman - Association

The previous UML class diagram shows a typical association where a Hangman object uses a WordLoader object to load a file with words. It also uses an instance of the RandomGenerator to select a random word from the list.

While in most cases aggregation is synonym with composition, there is a subtle difference.

Composition implies a relationship where the child cannot exist independent of the parent. In this relationship there is a strong lifecycle dependency between the child objects and the parent object. If a parent object is destroyed, its composed child objects will also be destroyed. In UML this is represented by a solid diamond followed by a line.

Composition

Aggregation implies a relationship where the child can exist independently of the parent. Aggregation is a specialized form of association where all object have their own life cycle. In UML this is represented by a hollow diamond followed by a line.

The aggregation link is usually used to stress the point that one class instance is not the exclusive container of the other class instance, as in fact the same class B instance can exist in (an)other container(s).

Aggregation

While a clear distinction is made here between aggregation and composition, it is not always done so in practice. In practice, one does often speak of composition even if he/she were to mean aggregation. As a result this course may also use the word composition where aggregation is meant. Of course in cases where a clear distinction is needed, the correct term will be used.

Definition - Association, Composition and Aggregation

To sum it up association is a very generic term used to represent when one class uses the functionalities provided by another class. It is sayed it's a composition if one parent class object owns another child class object and that child class object cannot meaningfully exist without the parent class object. If it can then it is called aggregation.

# Creating new Classes Through Composition

You probable have already been using composition without realizing it. Consider the example below of a class Product that might be used in an online web shop as a model for products that are sold.

Model of a Product

While developers will almost never document them as such in a UML diagram, the name and description attributes are actually instances of a class too, namely of std::string. So in other words Product is already a basic example of composition.

Model of a Product with Composition

When composing objects of other objects, the sub objects are generally made private. This hides implementation and allows the designer of the class to change the implementation if needed.

Consider the following example where a Canvas is modelled as a composition of a PixelDisplay object, which interacts with actual hardware, and Color objects that define the color of the foreground drawing and background.

A Display Device

By hiding the PixelDisplay object inside the Canvas, one hides the complexity of the hardware dependent class. This class may have methods for setting and resetting pixels, for changing hardware timings, for drawing rectangles, circles, and so on. All that is hidden only the ability to show some text, through display(text:string), and display a bitmap, through display(image:Bitmap) is made publicly available. This keeps the Canvas simple and very user friendly.

Also if one ever wanted to switch from a backlit LCD pixel display to an OLED display, the classes that use the Canvas never even have to change.

# Construction of Composite Objects

Whenever an object of a class is instantiated, a constructor is called. This however is not all that happens. When the object is composed of other objects, the constructors of those sub-objects are also called.

Take for example the class Motorcycle shown below, that is composed of several other classes, such as GearBox, Motor and Battery.

A Motorcycle Model

It is very important to know which constructors are called and at what time. Let's use the following implementation to illustrate which constructors are called when. Note that the constructors are implemented inline to shorten the code for this example.

# Battery

// battery.h
#pragma once
#include <iostream>

class Battery {
  public:
    Battery(void) {
      std::cout << "Constructing Battery" << std::endl;
    }
};
1
2
3
4
5
6
7
8
9
10

# Gearbox

// gearbox.h
#pragma once
#include <iostream>

class GearBox {
  public:
    GearBox(void) {
      std::cout << "Constructing GearBox" << std::endl;
    }
};
1
2
3
4
5
6
7
8
9
10

# Motor

// motor.h
#pragma once
#include <iostream>

class Motor {
  public:
    Motor(void) {
      std::cout << "Constructing Motor" << std::endl;
    }
};
1
2
3
4
5
6
7
8
9
10

# Motorcycle

A Motorcycle is a composed object of a Motor, a GearBoxand a Battery.

// motorcycle.h
#pragma once

#include <iostream>
#include "motor.h"
#include "gearbox.h"
#include "battery.h"

class Motorcycle {
  public:
    Motorcycle(void) {
      std::cout << "Constructing Motorcycle" << std::endl;
    }

  private:
    Motor motor;
    GearBox gearbox;
    Battery battery;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Basic Main Application

The main program could be as simple as:

//main.cpp
#include "motorcycle.h"

int main() {
    Motorcycle vn800;
    return 0;
}
1
2
3
4
5
6
7

Output

Constructing Motor
Constructing GearBox
Constructing Battery
Constructing Motorcycle

So basically, when constructing an object of a class, the default constructors of the composed objects are called first. When all composed objects are created, then the composing object is constructed.

Constructors Motorcycle and Sub Objects

Important to think about is:

  • Why are the constructors of the composed objects invoked before the actual constructor of the composing object ? Simple, because those objects should be ready and in a valid state for the composing object to use it when it is constructed. For example: the MotorCycle may want to change the battery voltage to a different level in its constructor. It would not be able to do this if the battery was not yet constructed.
  • The sub-objects are created in the order they are defined as attributes in the composing class.

This picture will become a bit more complicate

# Constructor Initialization List

By default, the constructors invoked are the default ("no-argument") constructors. Moreover, all of these constructors are called before the class's own constructor is executed.

But what if we do not want the default constructor to be invoked, or what if the composed object classes have no default constructors? In that case we need to be able to tell the compiler to execute a particular constructor when initializing the objects. This can be achieved using the constructor initialization list.

A constructor initialization list immediately follows the constructor's signature, separated by a colon :. Calling a different constructor a sub-object is accomplished by specifying the name of the attribute (object) followed by parentheses and the appropriate arguments.

# Composition of a Television

Let's for example take a Television class that contains two PowerSupply objects that can convert any input voltage to any output voltage. We keep it simple and just implement the constructors to see what happens when the objects are instantiated.

The header file of the PowerSupply looks like:

// power_supply.h
#pragma once

class PowerSupply {
  public:
    PowerSupply(double inputVoltage, double outputVoltage);

  private:
    double inputVoltage;
    double outputVoltage;
};
1
2
3
4
5
6
7
8
9
10
11

While the implementation is:

// power_supply.cpp
#include "power_supply.h"
#include <iostream>

PowerSupply::PowerSupply(double inputVoltage, double outputVoltage) {
    this->inputVoltage = inputVoltage;
    this->outputVoltage = outputVoltage;

    std::cout << "Constructing PowerSupply: Input = " << this->inputVoltage;
    std::cout << " Output = " << this->outputVoltage << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11

The class definition of the Television declares that it has two sub-objects of the type PowerSupply. One power supply for the embedded system and one for the LED display.

// television.h
#pragma once
#include "power_supply.h"

class Television {
  public:
    Television(double inputVoltage);

  private:
    PowerSupply embeddedPower;
    PowerSupply display;
};
1
2
3
4
5
6
7
8
9
10
11
12

Common Mistake

A common mistake for new C++ programmers is to try to call another constructor when defining the attributes as shown in the next code snippet:

// attributes
  private:
    PowerSupply embeddedPower(220, 3.3);    // Not possible !!!
    PowerSupply display(220, 12);           // Not possible !!!
1
2
3
4

A non-default constructor for sub-objects can only be called via the constructor initialization list.

Since the PowerSupply class has no default constructor, the Television class needs to use the constructor initialization list to call a specific constructor of PowerSupply. Note that it is the name of the attribute that is used and not the name of the class. Otherwise if multiple attributes of the same class would be available one would not be able to differentiate between them.






 







// television.cpp
#include "television.h"
#include <iostream>

Television::Television(double inputVoltage)
    : embeddedPower(inputVoltage, 3.3), display(inputVoltage, 12) {
    // Notice how we call the correct constructor
    // using the initialization list.
    // Multiple calls can be comma-separated

    std::cout << "Constructing Television"<< std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12

A example program can be as simple as:

// main.cpp
#include "television.h"

int main() {
    Television samsungTv(220);
    return 0;
}
1
2
3
4
5
6
7

Which would result in the output:

Output

Constructing PowerSupply: Input = 220 Output = 3.3
Constructing PowerSupply: Input = 220 Output = 12
Constructing Television

In practice most classes will have default constructors and if you need to change anything to the state of the internal objects you can often do this by calling the appropriate setters. However, if other constructors are available which initialize the sub-object to the wanted state, it is a good idea to use them as it keeps the code cleaner.

# LineSegment Example

Let's apply all this knowledge on a class LineSegment that models a line from a start point to an end point. In the previous section (constructors) we've build a Point class that is perfect for this.

The LineSegment can be composed of a start Point and an end Point as shown in the class definition:

// line_segment.h
#pragma once
#include "point.h"

namespace Geometry {

  class LineSegment {
  public:
    LineSegment(void);
    LineSegment(double x1, double y1, double x2, double y2);
    LineSegment(Point start, Point end);

  public:
    void set_start(Point start);
    void set_end(Point end);

  public:
    double length(void);

  private:
    Point start;
    Point end;
  };

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

By adding multiple constructors, an object of LineSegment can be instantiated using a both coordinates as well as Point instances for start and end.

A possible implementation for the LineSegment class could then be:

// line_segment.cpp
#include "line_segment.h"
#include <cmath>    // for sqrt

namespace Geometry {

  LineSegment::LineSegment(void) {
    // No need to do anything. Point's default constructors
    // will automatically be called
  }

  LineSegment::LineSegment(double x1, double y1, double x2, double y2)
    : start(x1, y1), end(x2, y2) {
      // Call specific constructors of the points.

      // We also could of called the move method here but
      // code is cleaner like this
  }

  LineSegment::LineSegment(Point start, Point end) {
    // Calling setters is cleanest here
    set_start(start);
    set_end(end);
  }

  void LineSegment::set_start(Point start) {
    this->start = start;
  }

  void LineSegment::set_end(Point end) {
    this->end = end;
  }

  double LineSegment::length(void) {
    return sqrt(
      (start.get_x() - end.get_x()) * (start.get_x() - end.get_x())
      +
      (start.get_y() - end.get_y()) * (start.get_y() - end.get_y())
    );
  }

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

with a small demo app being:

#include <iostream>
#include "line_segment.h"

using namespace std;

int main() {
  cout << "Line Segment demo" << endl;

  Geometry::Point start(7, 11);
  Geometry::Point end(-1, 5);

  Geometry::LineSegment line0;
  cout << "Line 0 has a length of " << line0.length() << endl;

  Geometry::LineSegment line1(start, end);
  cout << "Line 1 has a length of " << line1.length() << endl;

  Geometry::LineSegment line2(1, 3, -2, 9);
  cout << "Line 2 has a length of " << line2.length() << endl;

  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Output

Line Segment demo
Line 0 has a length of 0
Line 1 has a length of 10
Line 2 has a length of 6.7082
Last Updated: 9/25/2021, 7:48:21 AM