Association, Aggregation, and Composition in Object-Oriented Programming
In today's lesson, we will learn relational concepts in object-oriented programming. You can find several definitions of association, composition, and aggregation on the Internet but most of them have convoluted explanations with no practical applications.
These three terms describe the different relationships between classes. We will go through each of them one by one and by the end of this lesson, you will have a clear understanding of these relationships. Afterward, we will learn how to create diagrams that represent each of these relations among classes.
Association
In object-oriented general software design, the relationship between one object's functionality and another's is known as an association. Note that an association between two objects is not the same thing as inheritance between two classes. Association means that one object uses another object or a function/method in that other object. In other words, association is defined as the relationship between objects when one object has one or more references to other objects.
Below are the core differences between association and inheritance:
Inheritance implies that two objects are the same type of object. One object just happens to be either a more generalized or more specific version of the other object. Association occurs between two different objects.
Inheritance is said to be an IS-A relationship whereas association is known as a HAS-A relationship.
- The modality of inheritance depends on the programming language features. For example, Java does not support multiple inheritance, but C++ does. On the other hand, any language can have one-to-one, one-to-many, and many-to-many associations between objects.
There are two types of associations between objects: composition and aggregation. Let's understand what those are.
Composition
Composition is a form of association that occurs when an object's life is tightly bound to another object's life. When the main object dies (i.e., is deleted), all the objects that are associated with that object also die. This means that the referenced object is solely contained by the referring object.
There are a lot of real-life examples of composition in OOP. We are going to present the most used composition example here: the Vehicle example.
1// vehicle.go
2type Vehicle struct {
3 wheels [4]Wheel
4 doors [4]Door
5 seats [4]Seat
6}
7
8func NewVehicle() *Vehicle {
9 v := new(Vehicle)
10 for i := range v.wheels {
11 v.wheels[i] = Wheel{}
12 v.doors[i] = Door{}
13 v.seats[i] = Seat{}
14 }
15 return v
16}
17
18func (v *Vehicle) Mode(a, b Place) {
19 // Moving
20}
21
22// main.go
23func main() {
24 vehicle := NewVehicle()
25 // Do something with Vehicle
26 // The Wheel, Door, and Seat will be destroyed with the Vehicle when not in use
27}
Note that you cannot keep the Door
or any other object alive after destroying the Vehicle
object. This is why it's called composition: the Vehicle
class/object is composed of Doors, Wheels, and Seats.
Aggregation
Aggregation is the other form of association and is similar to composition. In aggregation, a container object again has several references to other objects. But, Aggregation is looser than composition. The objects' life cycles are not bound to each other in aggregation. Thus, the referring object may get destroyed before/after the referenced object.
Let's add some more properties to the Vehicle
class to demonstrate aggregation.
1// vehicle.go
2type Vehicle struct {
3 wheels [4]Wheel
4 doors [4]Door
5 seats [4]Seat
6 passengers []Person
7}
8
9func NewVehicle() Vehicle {
10 veh := Vehicle{}
11 for i := 0; i < 4; i++ {
12 veh.wheels[i] = Wheel{}
13 veh.doors[i] = Door{}
14 veh.seats[i] = Seat{}
15 }
16 return veh
17}
18
19func (v *Vehicle) GetIn(p Person) {
20 v.passengers = append(v.passengers, p)
21}
22
23func (v *Vehicle) GetOut(p Person) {
24 for index, val := range v.passengers {
25 if val == p {
26 v.passengers = append(v.passengers[:index], v.passengers[index+1:]...)
27 break
28 }
29 }
30}
31
32func (v *Vehicle) Move(a, b Place) {
33 // Moving
34}
In the above example, each Person
object that is inside the Vehicle
is not being created with the Vehicle
. When they get out of the Vehicle
, they are not destroyed. This is possible since there are other classes that also reference the Person
object. Let's see the main
method here.
1// Main.go
2type Main struct{}
3
4func (m Main) transport(a, b Place, person Person) {
5 vehicle := Vehicle{}
6 vehicle.getIn(person)
7 vehicle.move(a, b)
8 // Going out of scope, vehicle gets destroyed
9 // But person object is still alive
10}
11
12func main() {
13 main := Main{}
14 // Person being created before Vehicle
15 person1 := Person{}
16 vehicle := Vehicle{} // All the Composition objects (door, wheel) being created with Vehicle
17
18 // Aggregation happens here
19 vehicle.getIn(person1)
20
21 // Person being created after Vehicle
22 person2 := Person{}
23
24 // Another person object aggregating
25 vehicle.getIn(person2)
26
27 vehicle.getOut(person1)
28
29 main.transport(Place{'A'}, Place{'B'}, person1)
30
31 // Everything gets destroyed here
32}
In the above example, we can see in the main
method that the Person
objects can be created regardless of whether Vehicle
instantiation occurs. In the transport
method, we can see that even if the Vehicle
object is created and then deleted, the Person
object is alive in the method and after that.
Association vs Composition vs Aggregation
The key difference between association, composition, and aggregation is that composition and aggregation are two forms of association.

The key differences between composition and aggregation are given below:
Composition | Aggregation | |
---|---|---|
1 | Child will co-exist with the container class | Child can exist Independently |
2 | Part-of relationship. | Has-a relationship |
3 | Stronger form of association | Weaker form of composition |
4 | Can only have one-to-one and many-to-one relationship | Can have a one-to-one, many-to-one, one-to-many and many-to-many relationship |
5 | Composited objects cannot be changed from the referrer. | Aggregated objects can be removed or replaced by another same type of object. |
Build your intuition. Is this statement true or false?
You can delete one object and keep another if the relationship between them is association.
Press true if you believe the statement is correct, or false otherwise.
Object Typecasting
Now let's learn another concept in OOP called typecasting. Typecasting, or type conversion, is the process of converting a primitive data type to another primitive data type. In the OOP world, we can only cast objects to compatible types. In most languages, typecasting is automatically preferred. For example, if we use a float
as int
or double
, it will automatically typecast. See the example below:
1// Go code simulating the same scenario in the Java snippet with
2// two floats f1 and f2, that are converted to integers prior to addition
3package main
4
5import "fmt"
6
7func add(i1 int, i2 int) int {
8 return i1 + i2
9}
10
11func main() {
12 var f1 float64 = 0.5
13 var f2 float64 = 0.6
14 i := add(int(f1), int(f2))
15 fmt.Println(i)
16}
The type of conversion above is called automatic type conversion.
Other than the primitive types and built-in classes, user-defined interfaces or classes belonging to a single type hierarchy can be converted or cast into one another. However, if you attempt to convert objects with different types of hierarchy or different parent-child relationships, a compilation error will occur. At runtime, it is also possible to get a ClassCastException when you try to cast classes that are not in the same path in the class hierarchy.
In Java, if we set an object to one of its parents' reference types, then there is no way to call the parent object's methods that are overridden by the subclass. See the example below:
1package main
2
3import "fmt"
4
5type Shape interface {
6 Draw()
7}
8
9type rectangle struct{}
10
11func (r rectangle) Draw() {
12 fmt.Println("Drawing Rectangle")
13}
14
15type anyShape struct{}
16
17func (s anyShape) Draw() {
18 fmt.Println("Only Draw Shape")
19}
20
21func main() {
22 var shape Shape
23 // This is allowed due to polymorphism
24 shape = rectangle{}
25
26 // This will always call overridden method
27 shape.Draw()
28 // Output will be:
29 // Drawing Rectangle
30}
So how can we call a method of the Shape
class? We can do this by first casting the Rectangle
to Shape
and then calling it.
1// main.go
2func main() {
3 var shape Shape = new(Rectangle)
4
5 // We cast the rectangle into Shape, and then call methods
6 shape.Draw()
7}
Typecasting can be divided into two types:
- Upcasting: When you cast an object to one of its parent class types (i.e., from
Rectangle
toShape
). - Downcasting: When you cast an object to one of its subtypes (i.e., from
Shape
toRectangle
)
Parent of All Classes
In many programming languages like Java, there is always a class that is implicitly the parent of all the built-in and user-defined classes. Java calls it the Object
class. The Object
class is beneficial if you want to refer to any object whose type you don't know. Notice that the parent class reference variable can refer to the child class object (defined as upcasting in the previous section). We can instantiate an Object
object using the following method:
1obj := getObject()
The Object
class has many methods predefined in it, so any user-defined class should have those built-in methods in it without explicitly declaring or defining them. The methods are:
public final Class getClass()
public int hashCode()
public boolean equals(Object obj)
protected Object clone()
public String toString()
public final void notify()
public final void notifyAll()
public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout,int nanos) throws InterruptedException
public final void wait() throws InterruptedException
protected void finalize() throws Throwable
Using this Object
class, we can create an array of Objects of any type. See the code below:
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 objects := []interface{}{5, 5.5}
9 fmt.Println(objects[0])
10 fmt.Println(objects[1])
11}
12// Output:
13// 5
14// 5.5
Remember that you need to remember the types when you insert them into the containers (arrays) or else there would be no way to know what that Object was previously.
Let's test your knowledge. Click the correct answer from the options.
Which of the following methods is not already in a class?
Click the option that best answers the question.
- equals
- clone
- notifyAll
- waitForAll
Using UML to Represent Class Hierarchy
Object-oriented programming is used to design large systems with a lot of interconnected components. OOP is applied to desktop application design, web app development, and server program management. Top frameworks like .NET Framework
, Django
, Spring
, Unity 3D
all use object-oriented designs. All of these frameworks use thousands of classes to make the developers' lives a lot easier. For example, .Net Framework
has implementations of 6025 classes!
It is not easy to demonstrate the relationships between thousands of classes, so a Unified Modelling Language (UML) was invented to create and share class relations between collaborators. UML is the standard language for specifying, visualizing, constructing, and documenting the artifacts of software systems. UML is a pictorial language written with XML and has two major components for a diagram.
- Objects: Objects are the building blocks of UML. Typically, objects define classes and interfaces in the UML diagram.
- Relations: Relationships are mostly arrows from and to different objects in the diagram. They typically define inheritance, association, and modality.
UML is not just used to design OOP structures. It actually has many more uses, and there are lots of elements in UML that are not used for object-oriented design. We will leave those for now and only talk about the elements that are used for OOP.
Objects
UML can be used to create the following two diagrams for OOP design:
- Class Diagram
- Object Diagram
These components are nearly the same visually. The main difference is that Objects have underlinings under their name. Below is the visual representation of them:

Relations
Without relationships in UML, all the objects would be meaninglessly scattered in the diagram. Relations are depicted using lines and arrows that define the interactivity of different objects and classes. In the picture below, the most common relations are shown along with their meaning.

Now let's look at each one with an example:
Association
Whenever two classes are connected to each other, an association relationship link can be used. You can use a simple name for the relationship close to the line. For example, in a game, a player will have a lot of save files. If we consider Player
and SaveFile
as classes, then we can create an association link for them like below:

You can also state the multiplicity of the association with a value near the class. See that *
near the SaveFile
class above? That means that a Player
can interact with multiple SaveFile
classes. You can also use a number if you know how many save files a player will have prior to runtime (e.g. A Player
will always have N save files).
Aggregation
With aggregation, an object will always be referenced by other objects. This can be shown by an open diamond (a diamond without any fill color) in the UML. Previously we described aggregation and composition using the Vehicle class example. We will use the same example for the UML diagrams.

As you can see, a car can have multiple Passengers and one Driver within. Using the blank diamond, we can demonstrate that the lives of the Passengers and Drivers are not bound to the car.
Composition
If an object only contains one other object such that their lives are bound together, then we can show this relationship with the composition arrow in UML.

For example, a Car
will always have 4 Doors and 4 Wheels. Compositions are shown with a filled-in diamond shape.
Generalization
Generalization is a synonym of inheritance in the world of OOP. When a class is inherited from another class, then we can show this inheritance relationship with a simple arrow from the child class to the parent class.

Toyota Corolla and Ford Explorer are both popular cars in 2021. Since they are specific to Cars, they can be inherited from a generalization of Cars. Thus, they can be children of the general class Car
.
Realization
Realization is also a type of inheritance but for interfaces. In a realization relationship, one entity (normally an interface) defines a set of functionalities as a contract and the other entity (normally a class) realizes the contract by implementing the functionality defined in the contract.
For example, a car is Drivable
(people can drive it), so it should have a drive()
function. Let's look at a coded example:
1// Drivable.go
2type Drivable interface {
3 Drive()
4}
5
6// Car.go
7type Car struct{}
8// Must implement all functions of Drivable
9func (c Car) Drive() {
10 // Do the driving here
11}
This code can be presented in UML by the following picture.

Conclusion
Aggregation vs composition is one of the most common questions asked in interviews where the job requires skills in OOP. Whenever you are asked to do an object-oriented design, you must follow the rules of UML diagrams. This makes it easier for others to understand relationships in your code without having to know your notation. UML can save a lot of time during the sketch up of a system. This lesson provides a very basic understanding of some advanced topics of OOP and visualizing the structure of an OOP design.
One Pager Cheat Sheet
- By the end of this lesson, you will understand key
object-oriented programming
terms like association, aggregation, and composition and have a clear understanding of how to create diagrams representing these relations among classes. - Association is the relationship between two objects, which can be of the same or different type, and is usually represented as either a composition or an aggregation.
- Composition is a form of association in which an object's life is bound to that of its containing object, and all objects inside it die when the main object is deleted.
- Aggregation allows for the referencing of other objects in a container object, without affecting the life cycle of the referenced objects.
Composition and aggregation are two forms of association, with the main difference being that composition involves a stronger form of relationship than aggregation, and compositied objects cannot be changed from the referrer, while aggregated objects can be removed or replaced.
- The relationship between two objects is
association
, meaning that they have some kind of connection with no logical dependency enforced. - Object typecasting is the process of converting one primitive data type to another or one class to another compatible class in the same type hierarchy, which can be done automatically or by manually upcasting or downcasting.
- The
Object
class in Java is the parent of all classes and provides various useful methods such asgetClass()
,hashCode()
,equals()
andtoString()
that are inherited by all classes and can be referred to by a reference variable even if the type of the object is unknown. waitForAll
is not a method that is provided within Java'sObject
class.- UML provides a standard pictorial language for specifying and visualizing the objects and relationships of object-oriented designs, allowing efficient collaboration among software system collaborators.
- UML can be used to create Class Diagrams and
Object Diagrams
visually, the latter having underlinings under the Object's name. Relationships
in UML are represented with lines and arrows that define the interactivity of objects and classes, such as association, inheritance, aggregation and composition.- The
Player
andSaveFile
classes can be linked by anassociation
relationship, which can be indicated through multiplicity values near the related class. - Aggregation is shown in UML by an open diamond and can be demonstrated with the example of a car with multiple Passengers and one Driver, showing that their lives are not bound to the car.
- Objects can have a bound relationship, which is displayed as a
filled-in diamond shape
in UML diagrams, dubbed the composition arrow. - Using
generalization
to show inheritance between classes, we can use a simple arrow to represent the relationship betweenToyota Corolla
andFord Explorer
as children of a more generalCar
class. - A class
implements
an interface torealize
the functions defined by the interface, as shown by the example ofCar
implementing
Drivable
. - UML diagrams can provide a valuable tool when deciding between
aggregation
andcomposition
when usingOOP
, helping to visualize and understand relationships in code.