Aggregation and Composition

When determining the relationship between object at design time, it is easy to succumb to the habit of using inheritance; simply because it is easy.  In a language such as C++, it is even easier to do so because the language supports multiple inheritance.

Figure 1: the java.util.List Interface
Assume that Java does not have a Stack (java.util.Stack) class and you need to create a custom stack (call it MyStack).  You could do this in three different ways, but for the purposes of this paper I will discard the creation of the Stack class as shown in the image above.  Therefore, assume that there are only two ways.  The first way is to extend the ArrayList class.  The second way is to include an ArrayList object as a data member.  The first solution uses inheritance to solve the problem.  Remember, inheritance establishes an “is-a” relationship between objects.  Therefore, the relationship between MyStack and ArrayList is incorrect since a stack is not an array list.  The second solution is actually the best.  In fact, one of the rules of effective class design is to favor composition over inheritance.  But, what is composition exactly?  By definition, composition is the act of combining parts to form a whole (or a composite object).  Also, composite means something made up of distinct parts.  Because of this definition, it is said that composition enforces a “has-a” relationship.  For the second solution, this means than MyStack has an ArrayList that it uses to store data.

Figure 2: Association with Dependency

For now, let us assume that we do not know the exact nature of the relationship between MyStack and ArrayList.  In UML, the most generic relationship between two entities is called Association.  Association could be shown as a simple line between two classes or two objects.  Normally, this is done very early during the design phase.  The picture in Figure 2 shows an association relationship illustrating a dependency.  In this case, I cannot implement my stack if I do not have a list to hold my values.  The solid line indicates that it is a class dependency and not an interface.  This notation is also known as a unidirectional association.

Sometimes system designers will not further define association relationships.  This means that the association between entities will be left generic, which causes the developer to make a decision at implementation time which type of relationship will be used.  As I mentioned before, composition should be favored over inheritance.  Most times, this issue could be resolved by simply asking the question “is entity one a type of entity two?”  If the answer is no, then the association relationship will take the shape of either an aggregation or composition.  If the answer is yes, then the association relationship will take the shape of inheritance.  The rest of this handout will discuss aggregation and composition.

Aggregation and composition are highly debatable topics in UML.  We already defined composition as a “has-a” relationship.  The connotation of the use of the verb “has” implies possession or ownership.  On the other hand, an aggregation relationship in UML is defined as a “part-of” relationship; which has no connotations of ownership whatsoever.

Figure 3: Aggregation

Aggregation is a more specialized form of association.  In UML, the hollowed diamond goes to the object or class containing the reference to the other object in the relationship.  In this case, MyStack contains an ArrayList reference (just one as illustrated by the multiplicity value of 1).  This relationship establishes that the ArrayList reference is part of MyStack.  However, that reference could be shared by other instances of MyStack.

Figure 4: Composition

One could argue that composition is an even more specialized form of aggregation.  Not only an entity is part of another entity, but it is an exclusive part of the other.  For instance, a car has an engine. The relationship between an instance of car and an instance of engine is exclusive because the engine is not shared with other instances of car.  In this case, it makes sense to say that a car has an engine.  The “no sharing” rule is key to composition.

Another assumption you can make between aggregation and composition has to do with object destruction.  In the case of MyStack, if we designed MyStack to contain an exclusive instance of ArrayList, then it is safe to say that when invoking the MyStack destructor (know that Java does not have class destructors as C++) it is safe to invoke the ArrayList destructor.  This is because the specific instance of ArrayList is not shared.  However, because in an aggregation relationship the aggregated object is shared, it is not safe to invoke its destructor from the entity containing the reference.  In a language such as Java, the garbage collection mechanism is supposed to figure out when objects are no longer referenced in a program and reclaim resources used by such unused objects.  In C++, the responsibility of when to reclaim such resources is on the programmer.

It is really difficult to know when aggregation is more appropriate than composition or vice versa.  In my opinion, it is more natural to use composition.  However, computer resources are not infinite.  So, it is a good programming practice to determine during the design phase how objects are going to be used.  For instance, do I want my array list to be a shared resource?  Games often include lists of players’ stats.  The only way to guarantee that every player sees the same information is if the same list is shared.  For an implementation such as this one, it is perhaps best if aggregation is used.  The same could be achieved using composition.  However, it is very likely that the implementation will be slower, more error prone, etc (more ineffective).

In conclusion, composition should be favored over inheritance; especially in a language such as Java where classes could only have one immediate super-class. As far as aggregation vs. composition, the rules are not so easily defined.  Composition tends to be more natural.  Aggregation, when implemented properly, makes more effective use of computer resources because the aggregate object is shared.   However, this could lead to memory management issues in languages such as C++ (without an automatic garbage collection mechanism).  In contrast, components of a composite object could be destroyed safely when the composite object is destroyed.

Comments

Popular posts from this blog

Combining State and Singleton Patterns to Create a State-Machine

The Beauty of the Null Object Pattern

Exception Handling: File CRUD Operations Example