4.2 Implementing a New Class Using Aggregation


The Concept of Aggregation


The term aggregation describes a structure in which one component, the whole, contains the other components, the parts. In object-oriented programming terms, the contained components are sub-objects that are encapsulated within an enclosing object. The enclosing (outer, whole) object uses the functionality provided by the sub-objects to implement its own behavior. While the Location class seen in section 3.2 contained only built-in types (two ints), it is more common to find classes that use other classes, in addition to built-in types, to define their private data. Numerous examples of such classes will be seen in this chapter.

Aggregation is related to, but distinct from, association. The difference between aggregation and association is illustrated in the following figures that show the objects making up a timer system developed earlier. As shown in the following figure, creating the timer system by association involves creating the Clock, Counter, Message, Panel, Canvas, and Button objects individually, and using their methods to build up the desired structure of connections among them.

Using Assocation to Build a StopWatch

In this figure, the solid-line arrows show objects connected by pointers. The Counter object, for example, maintains a pointer to the Message object to which it communicates its current value. The dotted-line arrows in the figure represent the relationship between a Frame object and the user-interface components that are displayed in that Frame.

The next 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.


Using Aggregation to Build a StopWatch

A given class, for example the Clock class, may be used in several different aggregations. For example, a different timer system might not need user-control buttons. Such a system, a SimpleTimer, could be built using aggregation as shown in the following figure.


SimpleTimer Aggregation

Another timer system might be used only for the measurement of time relevant to the program and would not be displayed to the user. This timer system, an InternalTimer, could be organized as shown in the following figure.


InternalTimer Aggregation

Similarly, there might be a variety of systems that aggregate other combinations of these and other classes.

Advantages of Aggregation

Aggregation confers several advantages most of which flow from the use of encapsulation and they are important to recognize and reiterate:

simplicity:
The aggregating class or object allows the entire assembly of encapsulated sub-objects to be referred to as a single unit. This makes it easier to construct and manage multiple, independent instances of the system of sub-objects. Imagine, for example, the difference in building a system that has three independent timer systems. Using association, the code would directly manipulate fifteen different objects (three Clock objects, three Counter objects, three Textbox objects, and six Button objects) in addition to one Frame object. Using aggregation, the code would directly manipulate only three objects (three StopWatch objects) in addition to one Frame object.

safety:
Through encapsulation the sub-objects of the timer system are protected from accidental misuse by elements outside the timer system itself.

specialization:
The public interface of the StopWatch class provides operations that:

  • apply to several (or all) of the sub-objects as a group. For example, a single method in the StopWatch class can be defined to display the three user interface sub-objects (the Textbox and the two Buttons).
  • are named more meaningfully to distinuish them from the generic name of the sub-object's operation. For example, the number of timer intervals that have occurred when the timer has been activated is held in the Counter object. The Counter's internal number can be retrieved by applying the Value() method to the Counter object. However, it may be more meaningful to define an ElapsedTime() method that returns this value (by itself invoking the Counter::Value() method), as this name more directly conveys the intent of the method.

structure:
The existence of the encapsulating boundary captures the designer's intent that the components of the timer system are meant to function as a unit. Their organization and relationships are expressed directly in the StopWatch class, which can be studied and understood as a separate entity apart from any specific application.

substitution
An alternative implementation of the object defined by aggregation can be substituted without affecting other parts of the system as long as the public interface of the aggregating object remain unchanged.

It is clear from these advantages that developing skill in defining and implementing aggregations is an important goal.

Types of Aggregation

Two types of aggregation are described: static aggregation and dynamic aggregation. In static aggregation, the lifetimes of the sub-objects are identical to the lifetimes of the containing object. The sub-objects are explicitly declared in the class of the containing object, they are constructed when the containing object is constructed, and they are destructed when the containing object is destructed. In dynamic aggregation, at least some of the objects known only to the containing object are created dynamically, via the new operator, at run-time.

An intuitive example that illustrates the difference between static and dynamic aggregations is the contrast between an automobile, which has a fixed number of tires, and a tire store, where tires arrive from the factory and are sold to customers. For the automobile, the number of tires is known in advance. However, for the tire store, the type of its contents (i.e., automobile tires) is known, but the number of tires at any one time is variable and cannot be determined in advance.

Static aggregations are usually simpler, safer, and more efficient for a system to manage and thus should be used whenever possible. However, dynamic aggregations are needed for those equally important cases where the type, but not the number, of sub-objects is known at design time. Both forms of aggregation are useful, and a good designer must be able to distingusih between them and use whichever is appropriate for the problem at hand.

Defining a New Class

Aggregation, whether static or dynamic, inherently involves defining a new class. An object of the new class is the outer (encapsulating, whole) component of the aggregation. The sub-objects (the parts) are declared in the private data of the new class. To create high-quality aggregations it is necessary to be proficient in defining and implementing new classes.

A good class definition and implementation must have a number of desirable properites. Because a class captures an abstraction, the class must have all of the properties of a good abstraction. But as an executable representation a class must also have three major propeties beyond those of an abstraction:

correctness:
An object of the class must maintain its state properly and respond to invocations of its methods with the expected results. The most stringent level of correctness is a formal proof, though such proofs are usually reserved for safety-critical components due to the high cost of proving correctness of software. More common are less-stringent levels achieved through testing. No amount of testing establishes the formal correctness of a class. However, useful and measurable degrees of reliability and dependability can be achieved through rigorous testing.

safety:
When used in obvious ways, an object of a class should not produce unexpected or harmful consequences. In particular, the class should behave safely when passed as a parameter or when objects are assigned to one another. In C++, special constructors for copying and special actions for assignment can be given.

efficiency:
The object should make efficient use of processor and memory resources. While the most important means for ensuring efficiency reside in the overall system design and the choice of critical data structures and algorithms, the class implementor can improve the efficiency of the implementation in several ways such as declaring some elements to be constant and by using in-line methods.

Given the desired properites of abstractions and those of a class, it is clear that good object-oriented design is a creative and challenging activity.

The implementor of classes must be a proficient user of basic tools. In addition to the obvious need for a compiler is the need for proficiency in using a symbolic debugger during development and testing. Any system beyond the most trivial ones also requires the use of tools to (re)build the executable system from its source code when some part of that code has changed. For trivial systems all of the code can be recompiled every time any part of it is changed. But with modest and large systems, this brute-force approach is impractical because the time to recompile and relink the system is excessive. However, it is not practical for the implementor to remember all of the ways in which parts of the system must be rebuilt when some parts have changed. Thus, tools must be used to make the (re)building process efficient and accurate.

 

Implementation Issues:

Defining the Implementation

Organizing the Code

 

Kinds of Aggregations:

Simple Static Aggregation

More Complex Static Aggregation

Dynamic Aggregation

 




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

Legal Statement

 

 

 

ÿ