The term aggregation is frequently used to describe the structure of a class or object whose encapsulated data includes instances (objects) of other user defined classes. The term subobject is used to refer to each of the encapsulated objects.
Aggregation is related to, but distinct from, association. The difference between aggregation and composition is illustrated in the following figures that show the objects making up a timer system developed earlier. As shown in the next figure, creating the timer system by association involves individually creating the Clock, Counter, Textbox and Button objects, and using their methods to build up the desired structure of connections among these objects.
Composition |
---|
In this figure, the solid arrows show how the objects are connected through pointers. The StopButton object, for example, maintains a pointer to the Clock object that it controls. The dotted arrows in the figure represent the relationship between a Frame object and the user interface components that are displayed in that Frame.
The second the figure shows how the same system would appear when aggregation is used. With aggregation, the basic machinery of the timer system is encapsulated inside another object whose class, StopWatch, will be developed throughout this section. The public interface of the StopWatch class provides methods that allow the timer system to be manipulated as a whole.
Aggregation |
---|
A given class, for example the Clock class, may be used in several different aggregations. For example, a different kind of timer system might not need buttons to allow user control. This system, a SimpleTimer, could be built using aggregation as shown in the following figure.
Another Aggregation |
---|
Another timer system might be used only for the measurement of time relevant to the program and would not be display to the user. Such a timer system, an InternalTimer, could be organized as shown in the following figure.
Yet Another Aggregation |
---|
Similarly, there might be a variety of systems that aggregate together other combinations of these and other classes.
Ideally, no indirect control over the encapsulated objects should be allowed. However, this may not always be reasonable or possible. However, providing excessive indirect control over the encapsulated objects begins to weaken the advantages of aggregation. In the extreme case, the aggregating class relinquishes complete control of its subobjects, becoming little more than a weak wrapper to hold the subobjects together as a group. The designer of a good class must strike a balance between providing sufficient indirect control of the encapsulated objects so as to be usable in different applications and yet not so much indirect control as to lose the benefits of aggregation.
The designer of an aggregating class has several options to deal with the issue of subobject control. First, several similar classes can be designed, each providing a different degree of control. At the expense of creating and naming several classes, this approach allows a spectrum of choices for programmers who may have varying needs for control over the subobjects. Second, the interface of the aggregating class may use default arguments so that control reverts to the aggregating class itself if these arguments are not specified. While this approach also achieves a spectrum of choices, the argument lists become more complicated and more difficult to design. Due to the ordering of the default arguments, a user of the aggregating class may find it necessary to assume more control than is desired. Third, one or more auxiliary classes can be defined for specifying the control information. An instance of this auxiliary class is provided as an argument to the aggregating class. This argument may also have a default value or the auxiliary class may have a default constructor so that users may assume no indirect control. In the case of the StopWatch class, the arrangement of the user interface components can be collected into a StopWatchLayout class. This auxiliary class would contain all of the placement and labeling information for a StopWatch. The default StopWatchLayout constructor would provide standard (default) values for this information. The constructor for the StopWatch class would have a StopWatchLayout argument whose default value is a StopWatchLayout object constructed by the default constructor of the StopWatchLayout class.
StopWatch Class Interface |
---|
class StopWatch { private: Clock clock; Counter counter; TextBox tbox; StartButton start; StopButton stop; public: StopWatch( int interval, // timer interval Location where); // placement of UI objects void Start(); // program starts timer void Stop(); // program stop timer int ElapsedTime(); // reply time since first started void ShowIn(Frame& frame); // where to show UI objects ~StopWatch(); }; |
Notice that this design makes a number of decisions about the control issues raised earlier. This is not to suggest that these are the "right" decisions, only reasonable and illustrative ones.
The methods of the StopWatch class can be implemented as follows:
StopWatch Class Implementation |
---|
void StopWatch::Start() { clock.Start(); } void StopWatch::Stop() { clock.Stop(); } int StopWatch::ElapsedTime(){ return counter.Value(); } void StopWatch::ShowIn(Frame& frame){ frame.Display(start); frame.Display(stop); frame.Display(tbox); } StopWatch:: ~StopWatch(){} |
This code illustrate how the methods of the StopWatch class achieve their effect by manipulating the internal subobjects. For example, to display a StopWatch in a given Frame, the ShowIn method simply tells the Frame object to display each of the StopWatch's user interface subobjects. Similarly, the elapsed time of the StopWatch is found simply by querying the value of the Counter subobject.
The constructor for the StopWatch class was not shown above. This constructor will be shown in the next section where the concept of constructing subobjects is considered separately.
The constructor for a subobject may be related to the constructor of the aggregating class in one of three ways:
Subobject Construction |
---|
In the independent case, the subobject has a fixed constructor that does not depend in any way on the construction of the aggregating class. For example, in the StopWatch design the Counter subobject is always initialized to zero regardless of any other properties of the StopWatch object being constructed.
The direct case is illustrated by the Clock subobject in the StopWatch class. Here, the Clock object's constructor argument, the timer interval, is taken exactly from the StopWatch constructor argument. No change is made in this value.
The indirect case is illustrated by the construction of the user interface subobjects (StartButton, StopButton, TextBox). The StopWatch constructor has a single Location argument relative to which the three user interface objects are positioned. Assume that the user interface subobjects are positioned as shown in the following figure:
Layout of the StopWatch User Interface Subobjects |
---|
Using the layout picture above, the locations of the user interface subobjects would be determined as shown in the table below. The computation of the subobject locations involves invoking methods (Xcoord and Ycoord) of the StopWatch constructor argument (where), performing simple addition, and constructing a new (anonymous) Location object.
Location of User Interface Subobjects | |
---|---|
TextBox: | Location( where.Xcoord() + 10, where.Ycoord() + 10) |
StartButton: | Location( where.Xcoord() + 10, where.Ycoord() + 50) |
StopButton: | Location( where.Xcoord() + 70, where.Ycoord() + 50) |
In C++, the subobject constructors are placed as shown in this general form:
ClassName::ClassName( <argument list> ) : <subobject constructor list> { <body of constructor> }where <subobject constructor list> is a comma-separated list of constructors for subobjects. This list may contain only constructors, and only constructors for subobjects.
Using the layout decisions made above and the C++ syntax just introduced, the constructor for the StopWatch class can now be written as shown in the following figure.
StopWatch Class Constructor |
---|
StopWatch::StopWatch( int interval, Location where ) : // subobject constructor list count(0), // independent clock(interval), // direct // the following are indirect tbox( Location( where.Xcoord() + 10, where.Ycoord() + 10)), start(Location( where.Xcoord() + 10, where.Ycoord() + 50)), stop( Location( where.Xcoord() + 70, where.Ycoord() + 50)) // now the body of the constructor { clock.ConnectTo(&counter); counter.ConnectTo(&tbox); } |
|
Exercises |