3.2 Communicating Objects by Copy




An object being used for communication can be passed by copy as an input parameter to a method of a receiving object or the object can be the returned result of a method. Both of these cases will be illustrated by refining into a more object-oriented form the interface of the Frame class.

Objects as Input Parameters

The examples that follow make use of two new classes. In the original definition of the Frame class the location and shape of the frame were described by four integer values. Both of these concepts, however, can be captured in classes as follows:

   class Location {			// Version 1
     private:  
	// encapsulated implementation goes here
     public:
	Location(int x, int y);		// specific location
        Location();			// default location
     int Xcoord();			// return x-axis coordinate
     int Ycoord();			// return y-axis coordinate
   };

   class Shape {			// Version 1
     private:  
	// encapsulated implementation goes here
     public:   
	Shape(int width, int height);	// specific shape
	Shape();			// default shape
     int Height();			// return height
     int Width();			// return width
   };


The methods Xcoord, Ycoord in the Location class and Height and Width in the Shape class are often called "accessor" methods because they allow you to access, albeit indirectly, some information about the state of the object.

These new class can be used as follows:


   Location nearTop(20, 20), nearCenter(500, 500);
   Shape    smallSquare(50, 50);
   Shape    largeSquare(500, 500);
   

These two classes have two immediate advantages (five more will be seen shortly). First, they capture the concept of "location" and "shape". This is largely what "real" object-oriented programming is about - building classes that capture the concepts of some application. A good class need not be one that has an "important looking" interface. The Location and Shape classes are modest ones that cleanly capture a simple, but useful, concept. Second, the declarations above show that named objects can convey useful information to the reader about the intention of the programmer. The name "largeSquare" is more suggestive about the intention to have a window that is large and square than are the two integers 500 and 500 used in a parameter list with two other integers and one or more other values.

The Frame class can now be redefined to use the definitions of Shape and Location as follows:


  class Frame {				// Version 3
     private:  
          // encapsulated implementation goes here
     public:
	  Frame(Location p, Shape s);	// exact description
	  Frame(Shape s, Location p);	// exact description
	  Frame(Location p);		// default shape
	  Frame(Shape s);		// default location
	  Frame();			// all defaults;
     void MoveTo(Location newLocation); // move the window
     void Resize(Shape    newShape);	// change shape
     void Resize(float factor);		// grow/shrink by factor

	  ...				// other methods 
   };

The third advange of defining the Shape and Location classes is seen in the first two overload constructors. Since Shape and Postion are distinguished classes it is possible to define constructors that take them in either order. This cannot be done when the shape and location information is represented by four integer values. The fourth advantage is seen in the overloaded constructor "Frame(Shape s);". Such a constructor did not (and could not) exist in the earlier version (Version 2) of the Frame class. When the location and shape information is represented as four integers, what does a constructor with only two integers mean? Depending on how the four arguments are ordered it means either that the shape is missing or the location is missing, but it cannot mean both! However, both overloading are possible by introducing different classes that distinguish the two integers that are the shape from the two integers that are the location.

Frame objects can be created as follows:


   Frame  smallTop    (nearTop,    smallSquare);
   Frame  largeCenter (nearCenter, largeSquare);
   Frame  someWhere   (largeSquare);
   Frame  someSize    (nearCenter);
   Frame  anyKind;


These declaration of Frame objects illustrate the last three advantages of the Shape and Location classes. Fifth, the declarations are much more readable with the Shape and Location classes than without. Sixth, the Shape and Location objects (e.g., largeSquare and nearCenter) can be reused, avoiding the addition programming effort of remembering the exact coordinates of the "near to the center" point. Seventh, by changing the declaration of the Shape and Location objects (e.g., nearTop) the declarations of the Frames will then be adjusted accordingly. It is not necessary to go through the code looking for all the declaration of Frame objects and changing their integer parameters.

The Location and Shape classes have uses beyond those for which they was immediately conceived. Since the Location class captures a reasonably abstract notion - a point in a two dimensional coordinate system - it may be useful anywhere such a coordinate system appears. For example, just as the Location class helps to record "where on the screen should a window be placed" it can also be used by other interface items to record "where within a window should an item be placed".

The text and graphical items that can be displayed within a Frame also use the concepts of location and shape. For example, the DrawText method specifies that a given text string should be displayed at a given location. Also the DrawLine method specifies the endpoints (two locations) of a line segment. These and similar methods of the Frame class can also benefit from the Location and Shape classes as follows:



  class Frame {				// Version 3 (continued)
     private:  
            ...
     public:
          
            ...				// other methods

       void DrawText(char *text, Location loc);
       void DrawLine(Location end1, Location end2);
       void DrawCircle(Location center, int radius);
       void Clear();
       void Clear(Location corner, Shape rectangle);

	   ...
	    
   };

Notice that the Clear method uses both the Locatin ad the Shape classes as this method needs to specify both the placement and dimensions of a rectangular area within the Frame. This illustrates the pont made above: the Location and Shape classes are useful wherever a two dimensional coordinate systemis used to specify placement or where rectangular dimensions are required, whether this is information about a Frame on a screen or, equally well, an item displayed within that Frame.

Returning Objects by Copy: Frame Example (continued)

Objects can also be returned as the result of a method's execution. Returning an object, rather than a single primitive type, allows the method to communicate a complex entity as its result. Two examples are given to illustrate how objects are returned by copy. One example uses the Frame class and one example uses two new classes.

The TextSize method in the Frame class should be redefined to return an object as its result. The TextSize method computes the dimensions of the rectangular area occupied by a given text string. This computation depends on the font used by the Frame, the length of the string, and the characters in the string (some characters, like 'w' and 'm' are wider than other characters, like 'i' and 't'). The earlier Frame class declared this method as:


   class Frame {			// Version 1
	...
    public:
	...
	void TextSize (char *msg, int& width, int& height);
	...
   };

where the dimensions were returned as two distinct integer values. However, this definition has two problems:

The following example code that displays and then erases a text string illustrates how the TextSize method does not match the parameters of the Clear method:


        Frame    display;
	int width, height;
        char *msg = "Hellow World!";
	Location msgLocation(50,50);
	...
        display.DrawText(msg, msgLocation);
        ...
	display.TextSize(msg, width, height);
	Shape msgShape(width, height);
	display.Clear(msgLocation, msgShape);

Notice that the code writer must explicitly create the msgShape object. This must be done so that the two integer values modified by the TextSize method can be put into the form (a Shape object) that is required by the Clear method.

The TextSize method can be redefined to return a Shape object as follows:


	class Frame {			// Version 3 (continued)
	...
	public:
	   ...
	   Shape TextSize(char *msg);
	   ...
	};

Notice that this definition more clearly expresses the responsibility of the TextSize method: to compute and return an object (of the Shape class) that describes the dimensions of a rectangular area on the screen.

With this definition of the TextSize method, the earlier example of displaying and then erasing a text string can be written more succintly as follows:


        Frame    display;
        char *msg = "Hellow World!";
	Location msgLocation(50,50);
	...
        display.DrawText(msg, msgLocation);
        ...
	Shape msgShape = display.TextSize(msg);
	display.Clear(msgLocation, msgShape);

Notice that the returned result of the TextSize class now matches the parameters required by the Clear method. Also notice that the declaration of the msgShape object can be given at the point in the code whee the msgShape object is first used. Alternatively, the declaration could be given earlier as in:


	Shape msgShape;

	...

	msgShape = display.TextSize(msg);

	...

Some prefer to place the declaration at the point of first use, particularly if this is the only use of the object, because it helps to improve the readability of the code. Others prefer to place all declarations together at the beginning, particularly if the object is used several times in different places in the code, because this makes it easier to find the declaration of any object by simply looking in this one place. In some cases it is simply a matter of taste or style.

Revised Frame Class

All of the methods in the Frame class that can take advantage of passing information by copy have been redefined. The individual changes are collected together here:

  class Frame {				// Version 3
     private:  
          // encapsulated implementation goes here
     public:
	   Frame(Location p, Shape s);		// exact description
	   Frame(Shape s, Location p);		// exact description
	   Frame(Location p);			// default shape
	   Frame(Shape s);			// default location
	   Frame();				// all defaults;
     void  MoveTo(Location newLocation);	// move the window
     void  Resize(Shape    newShape);		// change shape
     void  Resize(float factor);		// grow/shrink by factor
     void  DrawText(char *text, Location loc);	// display text string
     Shape TextSize(char *msg);			// get shape of string
     void  DrawLine(Location p1, Location p2);	// draw line segment
     void  DrawCircle(Location center, 
                      int radius);		// draw circle
     void  Clear();				// erase entire Frame contents
     void  Clear(Location corner, 
                 Shape rectangle);		// erase rectangular area
          ~Frame();
   };

It should be clear that by using the Location and Shape classes the readability and utility of the Frame class has been significantly improved.

Returning Objects by Copy: File Dialog Example

The second example of methods returning objects as their results involves a class that represents a disk file, the File class, and three classes that embody different dialog methods for soliciting a file name from a user: FileQuery, FileChooser, and FileNavigator.

The File class captures the notion of a named, viewable body of text stored in the file system. The definition of the class is as follows:

  class File {
  private:
				// encapsulated implementation goes here
  public:

       File(char* fileName);               // represents file with given name
       File();                             // unknown, as yet, file
 char* Name();                             // reply name of file
 int   Exists();                           // does file Exist?
 void  View();                             // scrollable view window
 void  Edit(char* editor);                 // edit file using "editor"
 void  Delete();                           // delete from file system (gone!)
      ~File();                             // free name
};

The constructor allows the file to be given a name and the Name method allows that name to be queried. Because a file object may be created and not bound to a name and to guard against a user enterin the name of a non-existant file, the Exists method returns a value indicating if the file exists in the file system.

The View method opens a window on the screen within which the file is viewable. The user is able to scross horizontally and vertically through the file. The file can only be viewed, it cannot be changed. The file can be edited using the Edit method that takes as its parameter the name of the editor to be used to perform the editing. The file can be removed from the file system using the Delete method. After the Delete method has executed, the File object still exists, but the file itself does not.

The FileQuery class initiates a dialog with the user. The user is prompted to enter the name of a file. The FileQuery object returns a File object that represents the file named by the user. The FileQuery class is defined as follows:

 class FileQuery {
 private:
			// encapsulated implementation goes here

 public:

        FileQuery( char* path, char* filter );    // prompt with path and filter
        FileQuery( char* path );                  // prompt with path default filter
        FileQuery( );                             // use all defaults
   File AskUser();                                // get file from user via dialog
  ~FileQuery();
};
The constructors of the FileQuery allow a directory path (e.g., "/home/user") and a pattern for the expected file name. The pattern uses the traditional Unix "wild card" symbols. For example, the filter "*.ps" would describe any file with a ".ps" suffix. If not give, the path defaults to the current working directory and the filter defaults to any file (i.e., "*").

The FileQuery is very permissive. The path and filter information is provided as hints to the user, but they are not enforced. The user is free to enter any file name. Alternative, more restrictive and safer methods for soliticing a file name from the user are seen shortly.

The principle member function of the FileQuery class is the AskUser method. This method returns a File that is associated with the name entered by the user in the dialog initiated by the AskUser method.

An example of using the File and FileQuery class is the following:


   FileQuery query("/home/kafura", "*.ps");

   File file = query.AskUser();
   file.View();

In this example, the FileQuery object conducts the interaction with the user and returns a File object that is then presented to the user for viewing.

Objects of a given class may be returned from more than one other class. The FileQuery class defined above is only one way in which a File object may be produced as a result of a dialog with the user. The weakness of the technique used by the FileQuery class is that it relies heavily on the user's memory to recall the name of the file and the user's ability to enter without errors that name.

Two other classes for producing File objects use choosing and navigating techniques. Choosing means that the user is presented a list of files among which exactly one is chosen. Navigating means that the user is able to traverse the file tree in search of the desired file. The two classes below for choosing and navigating use a path name for the directory and a filter.

The FileChooser and FileNavigator classes are defined as follows:

   class FileChooser {
     private:
				// encapsulated implementation goes here
     public:

         FileChooser(char* path, char* filter);   // search at path with filter
         FileChooser(char* path);                 // search at path, no filter
         FileChooser();                           // search at CWD, no filter
    File AskUser();                               // get file via dialog
        ~FileChooser();                           // clean up
   };

   class FileNavigator {
     private:
				// encapsulated implementation goes here
     public:
         FileNavigator(char* path, char* filter);   // start at path using filter
         FileNavigator(char* path);                 // start at path, no filter
         FileNavigator();                           // start at CWD, no filter
    File AskUser();                                 // get file via dialog
        ~FileNavigator();                           // clean up
   };
An important aspect of object-oriented programming is seen in the public interfaces of the three classes FileQuery, FileChooser and FileNavigator: except for the difference in their names, they all have the same interface. The constructor arguments are the same as is the AskUser method. Each class provides the same functionality to the program though each achieves its functionality in a distinct way. However, the similarity of these classes does not allow them to be used transparently by the program. Due to the type checking, it is not possible, using the C++ language that we have seen so far, to interchange one with the other without rewriting the source code. We will see later that there are effective ways to organize and manipulate classes that have such similarity.



Next Stop


Exercises

  1. Give at least two other good names that could be used for the Shape class.

  2. Give at least two other good names that could be used for the Location class.

  3. Give the declarations of at least four Location objects that are on the top and left-hand side of the display.

  4. Give the declarations of at least four Shape objects that are thin rectangles that are short or long in length.

  5. Give the declarations or at least four Frame objects that use different combinations of the Location and Shape objects defined in the last two questions.

  6. Draw a picture of a screen showing how the Frame object declared in the last question would appear on the screen. Label each Frame in the picture with the name of the object that it represents.

  7. Write a program that creates a 200 by 200 size window near the middle of the screen with your complete name displayed near the middle of that window.

  8. Write a program that creates a 200 by 200 size window near the middle of the screen with your first name centered at the top of the window and your last name centered at the bottom of the window.

  9. Expanding/Contracting Line: Write a program to display a horizontal line of length 200 near the middle of the screen that contracts and expands as follows. Initially, each timer event causes the line to become shorter on both of its ends; the line should appear to be contracting towards its midpoint. The contracting continues until the length of the line is zero. Each timer event should then cause the line to become longer on both end; the line should appear to be expanding outward from its midpoint. Continue this contracting and expanding indefinitely. Experiment with the amount by which the line contracts and expands - pick an amount that "looks right" to you.

  10. File Viewer 1: Write a program that views a file whose name was selected by the user using the FileQuery class. Initially the user should see a window displayed on the screen with the message "Click Here to View a File". When the user clicks in the screen using the left mouse button a FileQuery object is used to obtain a File that is then made viewable.

  11. File Viewer 2: Write a program that views a file whose name was selected by the user using the FileChooser class. Initially the user should see a window displayed on the screen with the message "Click Here to View a File". When the user clicks in the screen using the left mouse button a FileChooser object is used to obtain a File that is then made viewable.

  12. File Viewer 3: Write a program that views a file whose name was selected by the user using the FileNavigator class. Initially the user should see a window displayed on the screen with the message "Click Here to View a File". When the user clicks in the screen using the left mouse button a FileNavigator object is used to obtain a File that is then made viewable.

  13. Write a program that edits a file whose name was selected by the user using the FileChooser class.

  14. Write a program that edits a file whose name was selected by the user using the FileNavigator class.

  15. Write a program that deletes a file whose name was selected by the user using the FileNavigator class.

Last Updated: September 10, 1996 / kafura@cs.vt.edu