3.5 A Simple Association


An association is one of the most basic structures in object-oriented systems. An association is a set of independent, interacting, collaborating objects. The objects are independent in that they must be constructed individually; each object has an identity and visibility apart from its role in the association. The objects interact in that the entire purpose of the association is to allow the objects to know about or be connected to each other. In an association an object can use the methods of any other object to which it is connected. Mechanically, passing objects by pointer or by reference is used to create the connections among objects. Finally, the objects are collaborating because they are configured so that they form a system that behaves in some coherent, coordinated manner.

Building systems by association promotes software reuse. If the objects forming the association can be created from existing classes, then the system can be constructed with little additional programming, as the objects are simply instantiated and hooked together to build the system. This method of building systems is often referred to as the plug-and-play technique because the builder simply plugs the objects together and plays with (experiments, validates, evaluates) the system. A well-designed class is one that has the potential to be (re)used in many different associations. Even when developing a class for a specific system, designers of classes often try to anticipate as wide a variety of situations where the class being designed may be useful.

The structure of an association is determined by how the responsibilities of the system are partitioned among the objects forming the association. By changing the mapping of responsibilities to objects, different structures can be derived for systems that achieve the same overall behavior. Creating a design in object-oriented programming often means determining the assignment of reponsibililites to individual objects or classes. Similarly, understanding the behavior of a given class often amounts to understanding what responsibilities that class assumes. Changing the responsibilities of a class can affect the class's implementation, the class's interface, and the ways in which objects of the class interact with other collaborating objects.

A simple example involving two classes will be used to illustrate a system created by association and the effects that altering responsibilities in this system has on the system's structure. While the "system" is trivially small, the concept that is being illustrated is not. The same concept is found, though in different degrees, in larger systems.

The example deals with a very small system - blinking text. The blinking-text problem is simply to display a string of characters that appear to blink. Web pages frequently have such blinking elements, whether text or graphics, to draw attention to themselves. The blinking effect is achieved by alternately displaying and erasing the string of characters from the screen. The time between these two actions determines whether the string blinks rapidly or slowly.

Three variations of this simple system are presented and discussed. In successive variations of the simple system, the class that represents the blinking-text abstraction is given successively more responsibility. The effect of this on the interface of the class and the other elements of the system is observed.

Step 1: Minimal Responsibility

A first solution to this problem assigns extremely little responsibility to the PrimitiveMessage class. This class is an abstraction of the blinking text, capturing only the text aspect of the blinking text. The PrimitiveMessage class is only reponsible for remembering the text. The text of the PrimitiveMessage must be given on construction and can be retrieved, via GetText, or altered, via SetText.


PrimitiveMessage class
class PrimitiveMessage
{
  private:
  public:
           PrimitiveMessage(char *text);
     void  SetText(char* newText);
     char* GetText();
          ~PrimitiveMessage();
};

The compete code necessary to display a blinking "Hello World!" text string is shown below. There is clearly some repetition in this code that comes from keeping track of the information about each of the two strings; it arises from the lack of responsibility assumed by the PrimitiveMessage class. The responsibilities of each party are analyzed below.

Blinking Text Using PrimitiveMessage Class
Frame window("Blinking Text", Location(100,100), Shape(200,200));
PrimitiveMessage greeting("Hello World!");
Location greetingLocation(20, 50);
int onoff;         // is text visible: yes=1, no=0
void OnStart()
{ window.Clear();
  window.DrawText(greeting.GetText(), greetingLocation);
  onoff = 1;
}
void OnTimerEvent()
{ if (onoff == 1) // text is visible
  { Shape greetingShape = window.TextShape(greeting.GetText());
    window.Clear(greetingLocation, greetingShape);
    onoff = 0;
  }
  else           // text is not visible
  { window.DrawText(greeting.GetText(), greetingLocation);
    onoff = 1;
  }
}
void OnPaint()
{ if (onoff == 1) // text is visible
     window.DrawText(greeting.GetText(), greetingLocation);
}

There are three participants in the Blinking Text system shown above: the PrimitiveMessage objects, the Frame object, and the simple programming environment functions (OnStart, OnTimerEvent, OnPaint). The responsibilities of these three participants is shown in the following table. In examining these responsibilities it might be concluded that the simple programming environment functions are bearing too much of the responsibilities of the system, whereas the PrimitiveMessage objects are bearing too little. It certainly makes sense to argue that the PrimitiveMessage object might reasonably be charged with keeping track of more of the information specifically related to its text and performing more of the actions specifically related to the management of its text.

Participant
Responsibility
PrimitveMessage
objects
text to display
Frame object draw/erase given
text at given location
simple programming
environment functions
when to draw/erase text
location/shape of text
state of text (visible)
Frame in which to display text

Step 2: More Responsibility

The responsibilities of the class that represents the blinking-text abstraction are increased so that the information and processing directly associated with the text are made part of the class. The class would then be responsible for maintaining the Location and Shape information for the text string and for clearing and drawing the text itself, all responsibilities that are closely connected with the text. One implication of this change is that the class must have access to a Frame object in order to (1) be able to draw and clear itself from this Frame object, and (2) to determine the Shape of the text, as this must be obtained from a Frame object using the Frame class's TextShape method. The revised set of responsibilities are shown in the following table.

Participant
Responsibility
Message object text to display
location/shape of text
Frame in which to display text
Frame object draw/erase given
text at given location
simple programming
environment functions
when to draw/erase text
state of text (visible)

Adding these responsibilities changes the interface of the class to that of the Message class developed earlier. The DisplayIn method in the Message class interface allows an association with a Frame class object. It is in this Frame object that the Message object will display itself and from which the Message object can discover the Shape of its current text. Because the Message class assumes responsibility for managing its Location, a method to change this Location is also part of the Message class interface.

The Message Class
class Message {
  private:
                //encapsulated implementation
  public:
                 
      Message (char *textString, Location whereAt);
      Message (Location whereAt);
void  DisplayIn (Frame&   whichFrame);
void  MoveTo (Location newLocation);
void  SetText(char* newText);
char* GetText();
void  Clear();
void  Draw ();
     ~Message ();
};

Notice that the DisplayIn method passes the parameter object whichFrame by reference; the & symbol following the class name "Frame" indicates the passing by reference.

The complete system for the blinking-text problem using the Message class is shown below. Because of the changes in responsibilities, the Message class assumes more of the responsibilities for its own self-management. As a result, the code for the OnTimerEvent() and OnPaint() methods are simplified.

The Blinking Text Using the Message Class
Frame window("Message Test", Location(100,100), Shape(200,200));
Message greeting("Hello World",  Location(20, 50));
int onoff;
void OnStart() 
{ window.Clear();
  greeting.DisplayIn(window);
  greeting.Draw();
  onoff = 1;
}
void OnTimerEvent() 
{ if onoff){greeting.Clear(); onoff = 0; }
  else     {greeting.Draw();  onoff = 1; }
}
void OnPaint() 
{ if (onoff) greeting.Draw(); 
}          

This version of the Blinking Text system is considerably simpler than the one that would result without the Message class. The Message class contains the machinery necessary for a Message object to manage itself more completely. Thus, to erase the text from the screen it is necessary only to tell the Message object to Clear itself - it is not necessary to manipulate the Frame object directly.

Step 3: Complete Responsibility

A more ambitious design might invest even more responsibility in the blinking-text abstraction. Notice that in the Message class design, the simple programming environment functions were responsible for remembering the state of the text (i.e., whether the text was visible of not). If this responsibility were shifted to the blinking text class itself, then the interface of the revised class, now named the BlinkingMessage class, would be as shown below.

BlinkingMessage Class
class BlinkingMessage {
  private:
                //encapsulated implementation
  public:
                 
      BlinkingMessage (char *textString, Location whereAt);
      BlinkingMessage (Location whereAt);
void  DisplayIn (Frame&   whichFrame);
void  MoveTo (Location newLocation);
void  SetText(char* newText);
char* GetText();
void  Blink();
void  Redraw();
     ~BlinkingMessage();
};

Notice that in the BlinkingMessage class the Draw() and Clear() methods have been replaced by a single method, Blink(). This replacement is in keeping with the notion that the objects of this class, not the users of those objects, are responsible for determining if the text is to be drawn or cleared. Also notice that a Redraw() method has been added to account for the need to be able to redisplay the BlinkingText without knowing whether or not the text is visible. The need for the Redraw() method can been seen in the OnPaint() method below.

The third, and final, version of the Blinking Text system is shown below using the BlinkingMessage class. Because the BlinkingMessage class assumed the responsibility for keeping track of its own state, the "onoff" variable is no longer needed in the code below.

Blinking Text Problem Using BlinkingMessage Class
Frame window("Message Test", Location(100,100), Shape(200,200));
BlinkingMessage greeting("Hello World",  Location(20,50));
void OnStart() 
{ window.Clear()
  greeting.DisplayIn(window);
  greeting.Blink();
};
void OnTimerEvent() 
{ greeting.Blink();
}
void OnPaint() 
{ greeting.Redraw();
}          

Comparing the Three Alternatives

The classes developed for the three versions of the blinking-text problem illustrate the difficulty of evaluating the tradeoff between the functionality of these classes and their reusability. The examples would seem to show that the BlinkingMessage class is the best because it allows the blinking-text problem to be solved in the most natural manner. However, a disadvantage of the BlinkingMessage class is that it is appropriate only when the text is fixed and blinking. If the text is varying or need not blink, then the fuctionality provided by the BlinkingMessage class is inappropriate. In other words, the BlinkingMessage class has sacrificed some degree of generality for an additional degree of ease in solving a particular problem. From this point of view, the BlinkingMessage class is an over-specialization. Overly specialized classes tend to work well when they are an exact match for the problem at hand but are not likely to be (re)used in problems that are close to, but not exactly, the one for which it was designed. From this point of view, the Message class strikes a better balance because it has an interface that can be used in more varied problems, though it may not be an ideal match to any one of them. In other words, the Message class has improved its reusability by limiting its specialization. This is not to say that the Message class is always to be preferred to the BlinkingMessage class - if one is solving a problem that contains repeated uses of the abstraction of blinking text then the BlinkingMessage class should clearly be used. Finally, although the PrimitiveMessage class appears to be to the weakest of the three alternatives, in situations where a line of text needs to be stored without any need for displaying it in a window, then both the BlinkingText and the Message class are disadvantageous because they contain unneeded functionality. In these cases, the PrimitiveMessage class may be preferable. The point of this evaluation is that many factors complicate the design process and the "best" design is often very difficult to determine.

These three classes, PrimitiveMessage, Message, and BlinkingMessage, suggest that a concept more powerful than association is needed. Why should a designer be forced to choose only one of these three possibilities? Why is it not possible to make the entire spectrum of possible choices available? This would be particularly desirable because, in some sense, the Message class is a specialization of the PrimitiveMessage class and the BlinkingMessage class is a specialization of the Message class. These questions can be answered by using the concept of hierarchy to develop a related set of classes.

Frame and Message Class Simulation

This applet illustrates the concept of forming associations between objects using objects of two classes, the Frame class and the Message class. The applet is divided by a vertical line into two areas. The left area, labelled "Class Space", contains an icon that represents the Frame class and an icon that represents the Message class. The right area, labelled "Object Space", contains icons that represent objects constructed from the Frame class or the Message class. Many object icons may appear in the Object Space. The same icon is used to represent the Frame class and the Frame objects to visually indicate their relationship. The same is true for the icon used to represent the Message class and objects of the Message class. This visual cue is important in making evident the class of each object.

A object of a class may be constructed as follows. Move the cursor to the class's icon in the Class Space and control-click (i.e., hold down the control key while clicking the mouse button). This will case a menu to appear that shows the signature of the class's constructor. A second control-click will remove the constructor menu. Subsequent conrol-clicks will toggle the menu between its visible and hidden states. When the constructor menu is visible, click on the menu item and a dialogue box will appear. The dialogue box has one text box at the top to enter the name associated with the object to be constructed (similar to the variable that names the object in a program) and text boxes for each of the constructor arguments. Parameters that are Location or Shape objects are denoted by suggestive labelling such as "Location: X" or "Shape: Width". Fill in each of the fields in the dialogue box and press the "Exec" button to construct the object. For each object constructed in this way, an icon for the object will appear in the Object Space part of the applet. This icon will be labelled with the string given as the object's name in the constructor dialogue box. The icon from an object may be repositioned in the Object Space by dragging the icon (placing the cursor over the icon and holding the mouse button down while moving the mouse). The placement of the icon in the object space has no significance (i.e., moving the icon does not affect the object corresponding to that icon).

An object may be manipulated as follows. Move the cursor over the object's icon in the Object Space and control-click to bring up a menu showing each method that can be applied to the object. Clicking on one of the methods in the menu causes a dialogue box to appear. Enter the parameters of the method and click on the "Exec" button to execute the method on the selected object. The control-click toggels the visibility of the menu of methods. An object may be deleted by a control-shift-click (clicking while hold down both the control and shift keys) on the object's icon.

An association between a Message object and a Frame object can be created. The association is formed by executing the DisplayIn method of the Message object. The dialogue box for this method asks for the name of a Frame object (i.e., the label of the Frame icon in the object space) in which the Message object will display its text. When the DisplayIn method is executed a line is drawn between the Message object and the Frame object. This line visually represents the logical association between the objects.

The code to generate the objects visible in the Object Space can be seen by performing a control-click operation in the Object Space outside of an object's icon. This action displays a window in which the code is presented in the form required by the simple programming environment.


Associations with Simple Counters and Timers


Several new classes will be introduced to illustrate building systems by association. These classes are a simple incrementing or decrementing counter, several kinds of buttons, and a simple timer. Though these classes are simple, they can be configured to form a number of interesting systems. The classes are also very specialized because only a part of the C++ language is being used to define them. Techniques that will be learned later can be used to extend the generality of these classes.

Simple systems of three or four objects will be built that count discrete events, either individual user interface actions or ticks of a timer. The existing Message and Frame classes will be used. In addition two new classes, Counter and Timer, will be defined and composed together in an association to form a small system. More complex examples are considered later.

The class Counter models a simple integer counter that can count upward or downward depending on how it is constructed. The Counter displays its current value in a Message, and if the Message object is itself displayed in a Frame object, the value of the Counter object will appear on the display. The Reset method allows the Counter object to be returned to its original state. The Counter class definition is:

The Counter Class
  class Counter {
     private:
                        // encapsulated implementation goes here
     public:
          Counter (int start, int end); // count up/down from start to end
          Counter();                    // count upwards from zero
     void Next();                       // increment/decrement by 1
     void Reset();                      // reset to original state
     void Reset(int nowThis);           // reset to specified value
     void ConnectTo(Message& msg);      // show current value here
         ~Counter();                    // destructor
   };

In the first constructor, the Counter counts upwards by one if "start" is less than "stop," and it counts downward by one otherwise. The second constructor defines a counter that counts upward by one without bound. The current value of the Counter is displayed in the Message object specified in the ConnectTo method. The current value of the Counter is incremented or decremented by the Next method. Whenever the value of the Counter is changed by the Next method, the Message object, if any, to which the Counter object is connect is updated accordingly using the Message object's ChangeMessage method. The Reset method causes the Counter object to be restored to its initial state.

A simple system that counts left-mouse-click events is shown in the code below. This system uses a Counter to record the number of left-mouse-click events, a Message object to display the Counter object's current value, and a Frame object to make this display visible to the user.

  Frame window("Counter", Location(100,100), Shape (200,200));
   Message countDisplay("", Location(10,10));
   Counter clickCount;
   void OnStart() {
     countDisplay.DisplayIn(window);
     clickCount.ConnectTo(countDisplay);
   }
   void OnPaint() {
     countDisplay.Draw();
   }
   void OnTimerEvent() {}
   void OnMouseEvent() {char *frameName, int x, int y, int buttonState) {
     if (buttonState & leftButtonDown) {
          clickCount.Next();
     }
   }

There are two significant things to observe in this example:

  • The OnStart function plugs the parts of the system together. The Message object is associated with the Frame object and the Counter object is associated with the Message object.
  • The OnMouseEvent has little processing to do: it simply informs the Counter object to performs its Next method whenever a left mouse click is detected.

When the Next event in the Counter object is called, a series of actions is triggered among the parts of the system so that the Counter object's internal count is incremented, its corresponding Message representation is changed, and the Message object changes what is being displayed in the Frame object visible to the user.

The Clock class is an abstraction of the system's interval timer. The Clock class increments a Counter at fixed intervals of time. The resolution of the Clock (i.e., the timer's interval) is set on construction. The Clock can be started and stopped. The definition of the Clock class is:

The Clock Class
  class Clock {
     private:
                // encapsulated implementation goes here
     public:
           Clock (int interval);        // milliseconds between "ticks"
      void ConnectTo(Counter& count);  // change count on each "tick"
      void Start();                     // (re)start Clock
      void Stop();                      // halt Clock
   };

The constructor specifies the interval of time, in milliseconds, between successive clock "ticks." On each tick of the clock, the Clock calls the Next method in the Counter to which the Clock is connected. The connection between the Clock and a Counter is established by the ConnectTo method. The Start and Stop methods can be used to control the Clock.

An example of how a Clock and a Counter can be used to build a simple timer system is the following:

    Frame   window ("Timer", Location(100,100), Shape(200,200));
     Message label("Seconds:", Location(10,10));
     Message display("", Location(100,10));
     Counter seconds;
     Clock   timer(1000);
   void OnStart() {
     timer.ConnectTo(seconds);
     seconds.ConnectTo(display);
     display.DisplayIn(window);
     timer.Start();
   }
   void OnPaint() {
     display.Draw();
   }
   void OnTimerEvent() {}
   void OnMouseEvent() {char *frameName, int x, int y, int buttonState) {}
 

This examples creates a one-second Clock connected to a Counter that counts upward from 0 (zero). The value of the Counter is presented in a Message labeled "Seconds" that is visible in the window Frame.

Frame, Message, Counter, Clock Simulation

This applet illustrates the concept of forming associations between objects using objects of the Frame class, the Message class, the Counter class and the Clock class. The applet is divided by a vertical line into two areas. The left area, labelled "Class Space", contains four icons that represents each of the four classes. The right area, labelled "Object Space", contains icons that represent objects constructed from the classes in the Class Space. Many object icons may appear in the Object Space. The same icon is used to represent each class and the objects created from that class to visually indicate their relationship. This visual cue is important in making evident the class of each object.

A object of a class may be constructed as follows. Move the cursor to the class's icon in the Class Space and control-click (i.e., hold down the control key while clicking the mouse button). This will case a menu to appear that shows the signature of the class's constructor. A second control-click will remove the constructor menu. Subsequent conrol-clicks will toggle the menu between its visible and hidden states. When the constructor menu is visible, click on the menu item and a dialogue box will appear. The dialogue box has one text box at the top to enter the name associated with the object to be constructed (similar to the variable that names the object in a program) and text boxes for each of the constructor arguments. Parameters that are Location or Shape objects are denoted by suggestive labelling such as "Location: X" or "Shape: Width". Fill in each of the fields in the dialogue box and press the "Exec" button to construct the object. For each object constructed in this way, an icon for the object will appear in the Object Space part of the applet. This icon will be labelled with the string given as the object's name in the constructor dialogue box. The icon from an object may be repositioned in the Object Space by dragging the icon (placing the cursor over the icon and holding the mouse button down while moving the mouse). The placement of the icon in the object space has no significance (i.e., moving the icon does not affect the object corresponding to that icon).

An object may be manipulated as follows. Move the cursor over the object's icon in the Object Space and control-click to bring up a menu showing each method that can be applied to the object. Clicking on one of the methods in the menu causes a dialogue box to appear. Enter the parameters of the method and click on the "Exec" button to execute the method on the selected object. The control-click toggels the visibility of the menu of methods. An object may be deleted by a control-shift-click (clicking while hold down both the control and shift keys) on the object's icon.

Several associations may be created among these classes. An association between a Message object and a Frame object can be created by executing the DisplayIn method of the Message object. An association between a Counter object and a Message is created by executing the ConnectTo method of the Counter object. An association between a Clock object and a Counter object is created by executing the ConnectTo method of the Clock object. The dialogue box for each of these methods asks for the name of the object (i.e., the label of the icon in the object space) with which the association is to be formed. When the method is executed a line is drawn between the two objects. This line visually represents the logical association between the objects.

The code to generate the objects visible in the Object Space can be seen by performing a control-click operation in the Object Space outside of an object's icon. This action displays a window in which the code is presented in the form required by the simple programming environment.

 

Tasks

  1. Using the Counter, Message, and Frame classes write, a program that counts left-mouse-button clicks and resets its count when a right-mouse-button click occurs.
  2. Using the Counter, Message, and Frame classes write a program that has two counters, one that displays a count of left-mouse-button clicks and one that displays the number of right-mouse-button clicks. Use Message objects to label the counts that appear on the display.
  3. Using the Counter, Message, and Frame classes write a program that implements a simple timer. Use the OnTimerEvent function to update the Counter object.
  4. Using the Counter, Message, and Frame classes write a program that implements a simple timer. Use the OnTimerEvent function to update the Counter object. Use a left-mouse-button click to control whether the OnTimerEvent updates the Counter object. The first left-mouse-button click starts the timing (subseqent OnTimerEvent function calls update the Counter object by using the Counter object's Next method). The second left-mouse-button click stops the timing. Thereafter, alternate left-mouse-button clicks start and stop the timing.
  5. Using the Clock, Counter, Message, and Frame classes write a program that displays elapsed time in two granularities using two Messages. One Message displays the value of a Counter that is incremented each second. The second message displays the value of a Counter object that is incremented each one-tenth of a second. Use two other Messages to identify the two changing values as "1 Second" and "1/10 Second". Use two different Clock objects.



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

Legal Statement

 

 

 

ÿ