4.6 More Complex Static Aggregation

In this section, the StopWatch class is implemented to illustrate a more complex aggregation, one in which the aggregation subobjects are more functional and in which the problems of properly constructing the aggregation are more challenging. Before considering the issue of how to construct this more elaborate aggregation, a general design issue is described, involving the degree to which the internal subobjects of the aggregation are implicitly revealed through the aggregation's public interface. This is termed the problem of indirect control.

The Problem of Indirect Control

When using aggregation, an important design issue is the degree of indirect control over the encapsulated objects that is reflected in the public interface of the aggregating class, indirect control being the ability of the object's user to affect the detailed organization or operation of the subobjects through the public interface of the aggregating class. In the StopWatch example the following indirect control questions arise:

  • At what Locations in a window are the two Buttons and the Textbox displayed?
  • What is the relative placement of the Buttons and the Textbox in a window? Buttons beside? Buttons below? Buttons above?
  • What is the timer interval? Is it fixed? user selectable? set by the program?
  • How many digits can appear in the Textbox?
  • What are the names of the Buttons as they appear in a window?
  • Does the StopWatch object always start at time zero or can it be given an initial value?
  • Can the Clock object be started/stopped by the program? only the user? both?

As can be seen, these questions touch on all of the encapsulated subobjects in the StopWatch class.

Ideally, no indirect control over the encapsulated objects should be allowed, but this may not always be reasonable or possible. Beware, though, that 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, yet not so much as to lose the benefits of aggregation.

The designer of an aggregating class has several options for dealing 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 promises 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, which 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.

Implementing the StopWatch Class

The definition of the StopWatch class is given below. The public interface provides methods to start and stop the StopWatch from the program level, to identify a Frame in which the StopWatch can display its user interface components (TextBox and Buttons), and to query the StopWatch for its current elapsed time.

StopWatch Class Interface
class StopWatch
    Button  startButton;
    Button  stopButton; 
    Clock   clock;
    Counter clockCount;
    Message clockDisplay;
    Panel   buttonPanel;
    Canvas  canvas;

    StopWatch(Frame& frame, Location where, 
                            int interval = 1000);
    void ButtonPushed(char* buttonName);
    void Tick();
    int ElapsedTime();

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, they are only reasonable and illustrative ones.

The methods of the StopWatch class can be implemented as follows:

StopWatch Class Implementation
void StopWatch::ButtonPushed(char* buttonName)
{ if (startButton.IsNamed(buttonName))
  if (stopButton.IsNamed(buttonName))

void StopWatch::Tick()
{ clockCount.Next();

int StopWatch::ElapsedTime()
{ return clockCount.Value();

StopWatch::~StopWatch() {}

This code illustrate how the methods of the StopWatch class achieve their effect by manipulating the internal subobjects. For example, for the StopWatch to start or stop itself in response to one of its buttons being pressed, the ButtonPushed method simply calls the Start() or Stop() method of the Clock subobject. 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 included above, but will be shown in the next section, where the concept of constructing subobjects is considered separately.

Constructing Subobjects

The constructor of an aggregating class must insure that its subobjects are properly initialized. It is expected, for example, that when a StopWatch object is constructed, all of its subobjects are also properly constructed and ready for use.

The constructor for a subobject may be related to the constructor of the aggregating class in one of three ways:

  • independent: The subobject constructor is fixed and independent of the arguments of the aggregating class.
  • direct: The subobject constructor depends directly on one or more arguments of the aggregating class's constructor.
  • indirect: The subobject constructor depends on one or more values computed from the aggregating class's constructor arguments.

These relationships are shown pictorially in the following figure.

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. Also the Start button, the Stop button, and the Message are constructed without reference to the StopWatch constructor values.

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 Canvas and Panel. The StopWatch constructor has a single Location argument relative to which these two 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 and Shape of User Interface Subobjects
Message: Location(60,10) in Canvas
(shape determined by number of digits)
StartButton: Location(10,10) in Panel
StopButton: Location( 70,10) in Panel
Canvas: Location(where.Xcoord() + 10, where.Ycoord() + 20) in Frame
Shape(130, 30)
Panel: Location(where.Xcoord() + 10, where.Ycoord() + 60) in Frame
Shape(130, 40)

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 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 follows.

StopWatch Class Constructor

Location StartButtonLocation = Location(10,10);
Shape    StartButtonShape    = Shape(50,20);

Location StopButtonLocation  = Location(70,10);
Shape    StopButtonShape     = Shape(50,20);

Location ButtonPanelLocation = Location(10,60);
Shape    ButtonPanelShape    = Shape(130,40);

Location CanvasLocation      = Location(10,20);
Shape    CanvasShape         = Shape(130,30);

Location ClockDisplayLocation= Location(60,10); 

StopWatch::StopWatch(Frame& frame, Location where,
                                   int interval)
: // sub-object constructor list
  clock("StopWatchClock", interval),
  buttonPanel( frame,  "ButtonPanel", 
               Location(where.Xcoord() +
                        where.Ycoord() +


  stopButton ("Stop",  

  canvas     ( frame,  "StopWatchCanvas", 
               Location(where.Xcoord() +
                        where.Ycoord() +

  clockDisplay( "0", ClockDisplayLocation)

{ // constructor body


  1. Add a label (a Message subobject) to the StopWatch class such that the label appears above the existing Message displaying the numeric value of the StopWatch. Add appropriate constructor arguments to the StopWatch constructor to give a character string for the label.

  2. Implement and test the SimpleTimer class described in section 4.2.

  3. Implement and test the InternalTimer class described above.

  4. Modify the StopWatch class so that it gives more control over the layout of the user interface subobjects by using default argument values on the StopWatch constructor.

  5. Implement and test the StopWatchLayout class described above.

  6. Implement and test a modified version of the StopWatch class that allows the layout to be customized by the user without changing the code. This customization should be achieved by reading the layout information from a file when a StopWatch object is constructed.

©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement