When teaching software design skills I often get asked what the difference is between aggregation and composition. My answer: parking lots.
Parking lots aggregate cars. A parking is still a parking lot even if there are no cars on it. It is a zero-to-many relationship and what is being aggregated is optional.
A car is an example of composition because it is composed of an engine, four wheels, seats, etc. By our definition a car ceases to be a car if it has no engine. It is a one-to-many relationship and what is being composed is required.
In the case of unmanaged code this was an even more important distinction because composing an object required the allocation of memory as well as taking responsibility for de-allocating the memory when it is no longer needed. Aggregating objects do not have this constraint and it would be possible to allocate an aggregate object and allow another entity to be responsible for freeing up or deleting the object. In managed code this distinction becomes less important because we don’t have to worry about freeing memory; the system takes care of it for us.
In managed code there may be no distinction between aggregation and composition but there is still an important semantic distinction between them so I still call them out in the UML because it helps clarify the nature of the relationships. In the UML, aggregation is indicated with an open diamond whereas composition is indicated with a filled in diamond.
We refer to aggregation as a “has-a” relationship. I sometimes refer to composition as a “has-to has-a” relationship. If I don’t want to be that precise in the UML I’ll simply draw a line from one class to another that indicate invoking a method without saying anything about the relationship between the objects.