Lecture Notes on Object-Oriented Programming

The Quality of Classes and OO Design

This collection of notes on OOP was never meant to stand alone. It also represents a view of OO circa early to mid 1990s. Some people still find them useful, so here they are, caveat emptor. Special thanks to Gilbert Benabou for taking to time to compile the first printable version of this document and inspiring us to provide it.
[PDF]
Printable Version

Different authors use different terms for the idea of containment. UML has a very specific use (and different symbols) of the terms aggregation and composition, though this is not necessarily consistent with OO authors. For the record, UML aggregation is "weak containment", usually implemented with pointers, and the symbol is an open diamond. Composition is "strong containment", implemented by value, and the symbol is a filled diamond.

An aggregate object is one which contains other objects. For example, an Airplane class would contain Engine, Wing, Tail, Crew objects. Sometimes the class aggregation corresponds to physical containment in the model (like the airplane). But sometimes it is more abstract (e.g. Club and Members).

Whereas the test for inheritance is "isa", the test for aggregation is to see if there is a whole/part relationship between two classes ("hasa"). A synonym for this is "part-of".

Being an aggregated object of some class means that objects of the containing class can message you to do work. Aggregation provides an easy way for two objects to know about each other (and hence message each other). Can the contained object message the container object? Is the relationship symmetrical?

Should the aggregation be done by value or by reference?

By value means that the lifetimes of the objects are exactly the same; they are born and die at the same time; they are inseparable during their lifetimes. Aggregation by value cannot be cyclic: one object is the whole, one the part.

By reference de-couples the lifetimes of the two objects. The whole may not have a part, or it may have a different part at different times. Different wholes may share the same part. De-allocating the whole won't automatically de-allocate the part. Aggregation by reference allows for the part to be aware of its whole.

Example

A List class may look like this. This list stores integers.

class List {
public:
 List();
 void addToFront(int);
 int firstElement();
 int length();
 int includes(int);
 void remove(int);
private:
 // some data structure for 
 // storing integer elements
};

The List class could be quite handy for creating a new Set class, since the behavior is similar. One way to do this is to use a List object to handle the data of the Set, via aggregation.

class Set {
public:
 Set();
 void add(int);
 int size();
 int includes(int);
private:
 List data;
};

Now we can pretty easily implement Set in terms of the functionality available in List:

int Set::size() {
 return data.length();
}
int Set::includes(int newValue) {
 return data.includes(newValue);
}
void Set::add(int newValue)
{
 if( ! includes(newValue) )
 data.addToFront(newValue);
 // else, no-op, since sets can only have 
 // one copy of any given value
}

This shows composition/aggregation by value, since the List element ivar of Set is statically declared. Every Set object always has a List object. Delete a Set, the List goes away too, with no special action required.

Note that you need to worry about the List ivar object being properly initialized, so the constructor to Set must do this:

Set::Set() : data()
{
 // other stuff for Set, as needed
}

The section on Inheritance will consider a design alternative for our implementation of Set.

UML representation

UML distinguishes between composition and aggregation. (These are often synonymous terms.)