Background:
Both terms — composition and aggregation — come from classical object-oriented design. They describe relationships between objects: "whole-part", but differ in the degree of interdependence of the objects.
Problem:
In large systems, code readability and extensibility are important. Composition and aggregation help establish clear dependencies between classes. It is important to understand the difference — otherwise, one can make the architecture too rigid or, conversely, vague.
Solution:
Composition is a strong "part-whole" relationship. When an object is contained only within another object and cannot exist outside it. In Python, such an object is usually created and managed within a "container" class.
Aggregation is a weaker relationship. The "part" object can exist separately from the "whole" object and be passed into it from the outside.
Code Example:
class Engine: def start(self): print("Engine started") class Car: # Composition: Engine is created inside Car def __init__(self): self.engine = Engine() def drive(self): self.engine.start() my_car = Car() my_car.drive() class Wheel: pass class Bicycle: # Aggregation: wheels are passed in from outside def __init__(self, wheels): self.wheels = wheels w1, w2 = Wheel(), Wheel() bike = Bicycle([w1, w2])
Key features:
If the "container" object (for example, Car) is deleted, will the Engine object be deleted?
In composition, if no one references the "part" object anymore, it will be removed by the garbage collector. In aggregation, the "part" object can continue to exist (it may still be referenced by other objects).
Does Python have a special syntax for aggregation and composition, like in other languages?
In Python, there are no keywords indicating explicit composition/aggregation. It all depends on how the objects are created and their lifecycle.
Can composition be changed to aggregation and vice versa without rewriting the logic?
Not always. If the logic requires complete control and uniqueness of the "part" object, simply replacing composition with aggregation may lead to errors (for example, if the wheels for different bicycles become shared instances of the same class).
Pros:
Negative case: In a project, an external engine object was created for each car and passed into the car through aggregation. However, later the object references were mixed up, and engines were swapped between cars arbitrarily — which led to confusion and bugs.
Pros: the architecture seemed flexible. Cons: it was difficult to determine which engine belonged to which car.
Positive case: In another project, engines were created inside the Car class (composition); cars directly managed their engines, ensuring reliability.
Pros: no leaks and logical confusion. Cons: more code was needed to manage the lifecycle of each car and its components.