I always thought that aggregation is synonymous with composition, but I came across a blog on the Internet where shows the differences between composition and aggregation .
It blew me away. Please explain the pros / cons of both with small examples. How does this affect extensibility, testability, etc.
Answer 1, authority 100%
There are several types of interaction between objects, united under the general concept of “Has-A Relationship” or “Part Of Relationship”. This relationship means that one object is an integral part of another object.
There are two subspecies of this relationship: if one object creates another object and the lifetime of the “part” depends on the lifetime of the whole, then this is called “composition”, if one object receives a reference (pointer) to another object during construction, then this is already an aggregation.
Let’s take a look at an example from the .NET Framework to see the limitations / implications of this relationship:
StringWriter class is a specialized version of the
TextWriter class, which is used extensively in serialization and to get the textual representation of objects.
StringWriter creates a string representation of an object or graph of objects and relies on an instance of
StringBuilder to work. Those. we can say that ‘StringWriter HAS A StringBuilder’ or ‘StringBuilder is part of StringWriter’. Now let’s see how to decide whether
StringWriter should receive an instance of
StringBuilder from the outside or create one?
On the one hand, as a client of the
StringWriter class, we often do not care what exactly is used inside this class to obtain a string representation. This means that for ease of use, it is best for
StringWriter to instantiate
StringBuilder on its own.
But, on the other hand, a specific
StringWriter object can only be responsible for getting part of the string representation, and the other part of the string can be calculated in a different way. From this point of view, it is best for
StringWriter to take an instance of
StringBuilder in the constructor. The same is true for high-load systems, in which it is reasonable to use an object pool.
StringWriter is a library class that must support both scripts, it has overloaded versions of the constructor: one of them creates a
StringBuilder inside, and the other accepts it outside.
In other words, choosing between composition and aggregation is based on balancing different design requirements:
Composition: Object A controls the lifetime of Object B
Composition allows you to hide the object usage relationship from the client’s eyes.
Makes the API for using a class simpler and allows you to move from using one class to another (for example,
StringWritercould change the implementation and start using a different type, for example
- The relationship is rather rigid, since one object must be able to create another: it must know a specific type and have access to the creation function. Composition does not allow interfaces (without involving factories) and requires the class to have access to the constructor of another class: Imagine the constructor
StringBuilderis internal, or it is the interface
IStringBuilderand only the client code knows which instance should be used here and now.
Aggregation: Object A gets a link to Object B
Loose coupling between an object and its client. Now we can use interfaces and one object does not need to know how to create another object.
Great flexibility. Follows from the first paragraph
Exposing implementation details. Since the client of the class must provide a dependency at the time of object creation (pass an instance of
StringBuilderat the time of creation of
StringWriter, the very fact of this relationship becomes known to the client.
The first point implies an increase in the complexity of the work of clients, as well as a greater “toughness” of the decision in the long term. Now the author of the
TextWriterclass can no longer make a decision on his own and move from
StringBuilderto something else. Yes, you can “add another layer of abstraction” and highlight the
IStringBuilderinterface, but breaking this relationship will not be possible without breaking all existing clients.
In conclusion: design is about finding a compromise between various factors. Composition is simpler from the point of view of the clients of the class, but it imposes certain restrictions: the “whole” must be able to create a “constituent part”. Aggregation is more flexible, but it imposes other restrictions: now the “whole” does not hide the existence of a “component”, and therefore cannot replace it with another “component” in the future.
P.S. If you really want to use an example from the real world, then a screwdriver may be suitable to explain composition and aggregation. If the screwdriver is solid, i.e. the handle and the nozzle are tightly connected to each other, then we have a composition relationship. If the nozzle is removable and can exist without a handle or be used with another handle, then we have a aggregation relationship.