3.5 Building Systems by Association




Association can be used to build systems of interacting, collaborative objects. A robust collection of classes allows the system to be constructed with little additional programming, the objects are simply instantiated and hooked together to build the system. This method of building systems by composition 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.

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 defined them. Techniques that will be learned later can be used to extend the generality of these classes.


Simple Counters and Timers


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 to new classes, Counter and Timer, will be defined. These classes are defined so that they can be 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 upwards or downwards depending on how it is constructed. The Counter displays its current value in a Message. 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 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 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 it initial state.

A simple system that counts left mouse click events is shown in the code below. This system uses a Counter to count 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:

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 interal 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 systems 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);
     second.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 upwards from 0. The value of the Counter is presented in a Message labeled "Seconds" that is visible in the window Frame.

To allow systems to be created that give the user interactive control three button classes will be defined. These buttons can be composed with the Clock and Counter classes to form several small, but interesting systems.

The three button classes are very similar, each being specialized to a particular purpose. Two of the button classes allow the user to start and stop a Clock and the third allows the user to reset a Counter. The definitions of these classes is as follows:


   class StartClockButton {
     private:
				// encapsulated implementation goes here
     public:
	   StartClockButton(char* label);	// label of button on screen
      void ConnectTo(Clock& t);			// start this Clock
          ~StartClockButton();			// destructor
   };


   class StopClockButton {
     private:
				// encapsulated implementation goes here
     public:
	   StopClockButton(char* label);	// label of button on screen
      void ConnectTo(Clock& t);			// stop this Clock
          ~StopClockButton();			// destructor
   };


   class ResetCounterButton {
     private:
				// encapsulated implementation goes here
     public:
	   ResetCounterButton(char* label);	// label of button on screen
      void ConnectTo(Counter& t);		// reset this counter
          ~ResetCounterButton();		// destructor
   };

These classes are very specialized. Techniques will be studied later that will allow classes that are so similar to be defined in a more general way.

Objects of the button classes need to be made visible to the user in some window. The Frame class is again extended to include methods for displaying button objects. As before, the Display method is overloaded. Only the most relevant methods are shown below.


     class Frame {
       private:
		// encapsulated private implementation
       public:

         void  Display(Message msg);               // show message in window
	 void  Display(TextBox& box);              // show text in window
	 void  Display(StartClockButton& button);  // show button in window
	 void  Display(StopClockButton& button);   // show button in window
	 void  Display(ResetCounterButton& button);// show button in window

     };
The Frame class definition is clearly becoming populated with many methods. Having a separate overloading of the Display method for each distinct kind of button will quickly become intolerable - imagine how many different kinds of buttons you have seen in user interfaces. Fortunately, other parts of object-oriented programming and C++ are available, and will be seen shortly, that will remedy this problem.

A small system that allows the user to control a counter extends the simple example above by adding a button to start the Clock.


  //declarations

     Frame   window (Location(100, 100), Shape(300, 200));
     TextBox display(Location(10,10),    Shape(150, 50), "Seconds:");
     Counter seconds;
     Clock   timer(1000);
     StartClockButton start("Push to Start");

  // code

     start.ConnectTo(timer); 
     timer.ConnectTo(seconds);
     seconds.ConnectTo(display);
     window.Display(display);
     window.Display(start);


This system is the same as the one developed above except that the program control of when the Clock is started has been replaced by interactive user control.

The concepts of building a system of interacting objects using association are illustrated by this applet .



Next Stop


Exercises

  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 other 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 composition, construct a system that displays three TextBoxes labeled "Units", "Tens" and "Hundreds". Each Textbox displays the value of a different Counter. The Counter in "Tens" should be incremented once for every ten times the counter in "Units" is incremented; similarly for "Hundreds" and "Tens". Use a triply nested loop so that the "Units" Counter starts at 0 and ends at 1,000,000.

  6. Using composition, construct a system that has a one second Clock displayed on the screen which can be both started and stopped by the user.

  7. Using composition, construct a system that has two Clocks, one operating on a one second interval and one on a 0.1 second interval. Each Clock is connected to a different counter and each can be controlled by start and stop buttons. Both counters appear on the screen with appropriate labels.

  8. Using composition, construct a system that has a one second Clock displayed on the screen which can be both started, stopped and reset. To reset the Clock, the user must first stop the Clock, enter a new time in the TextBox, press the reset button and then press the start button.

Last Updated: August 20, 1996 / kafura@cs.vt.edu