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 the interface of the Frame class into a more object-oriented form.

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 shown in the following tables.

The Location Class
									
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 };

The Shape Class
  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" or "query" methods because they allow, albeit indirectly, information about the state of the object to accessed or queried.

Objects of these new classes can be declared 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 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 shown in the following figure.


Frame Class (Version 3)
class Frame {                         // Version 3
  private:  
          // encapsulated implementation goes here
  public:
          Frame(char* name, Location p, Shape s);       // exact description
          Frame(char* name, Shape s, Location p);       // exact description
          Frame(char* name, Location p);                // default shape
          Frame(char* name, Shape s);                   // default location
          Frame(char* name );                           // name only
          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(char* name, Shape s);" which 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 overloadings 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    ("Square Near Top", nearTop,    smallSquare);
   Frame  largeCenter ("Big at Middle", nearCenter, largeSquare);
   Frame  someWhere   ("Big Somewhere", largeSquare);
   Frame  someSize    ("At Middle", nearCenter);
   Frame  anyKind     ("Name Only - Rest Defaults");


These declarations of Frame objects illustrate the last three advantages of the Shape and Location classes.

The fifth advantage is that 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 additional 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 were 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 a window should be placed it can also be used by other interface items to record where within a window an item should 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 fshown in the next figure.


Frame Class (version 3) Continued
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 Location and the Shape classes, as this method needs to specify both the placement and dimensions of a rectangular area within the Frame. This illustrates the point made above: The Location and Shape classes are useful wherever a two-dimensional coordinate system is used to specify placement or where rectangular dimensions are required, whether this is information about a Frame on a screen or an item displayed within that Frame.

Frame (version 3) Simulation

This applet illustrates the concepts of object construction and object manipulation using version 3 of the Frame class. The applet is divided by a vertical line into two areas. The left area, labelled "Class Space", contains an icon that represents version 3 of the Frame class. The Frame class icon is the only one which appears in this area in this version of the simulation; later versions will introduce additional classes. The right area, labelled "Object Space", contains icons that represent objects constructed from the Frame 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. This visual cue is important in later versions of the simulation where there are many classes and the relationship between an object and its class would not otherwise be clearly evident.

A Frame object may be constructed as follows. Move the cursor to the Frame 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 Frame'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. Two visible actions will occur when the object is constructed. First, 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. Second, a window will appear whose title, position, and dimensions correspond to those given in the constructor dialogue box. This part of the simulation emphasises that objects are created by using the constructor of a class and that objects correspond to real entities (in this case a visible window). The icon from a Frame 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 move the window corresponding to that icon).

A Frame object may be manipulated as follows. Move the cursor over the Frame icon in the Object Space and control-click to bring up a menu showing each method that can be applied to a Frame 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. To see the mouse events that occur within the window corresponding to a Frame object, shift-click on the object's icon. This activates a small display that shows the current mouse coordinates when the cursor is within the window and the current mouse button state. A Frame object may be deleted by a control-shift-click (clicking while hold down both the control and shift keys) on the Frame object's icon.

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.

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 method does not clearly capture the responsibility of the TextSize method to return the dimensions of a rectangular area as well as if a Shape object were used to convey this information, and
  • The TextSize parameters do not match closely the parameters of other related methods (e.g., the Clear method) in the Frame class.

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 = "Hello 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 succinctly as follows:

       Frame    display;
        char *msg = "Hello 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 where the msgShape object is first used. Alternatively, the declaration could be given earlier as in:

       Shape msgShape;
        ...
        msgShape = display.TextSize(msg);
        ...

Some programmers 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 in the table below. It should be clear that by using the Location and Shape classes the readability and utility of the Frame class has been significantly improved.

The Frame Class (Version 3)
									
class Frame { // Version 3 private: // encapsulated implementation goes here public: Frame(char* name, Location p, Shape s); // exact description Frame(char* name, Shape s, Location p); // exact description Frame(char* name, Location p); // default shape Frame(char* name, Shape s); // default location Frame(char* name ); // name only Frame(); // all defaults; int IsNamed(char* aName); // is this your name? 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(); };

Returning Objects by Copy: File Dialogue 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 shown below.

The File Class
 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 entering the name of a non-existent file, the Exists method returns a value indicating whether 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, but the file can only be viewed, not 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:

The File Query Class
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 given, 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 soliciting a file name from the user will be explored 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 dialogue 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:

The FileChooser and FileNavigator Classes
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 dialogue
        ~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 dialogue
        ~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 use the same interface. The constructor arguments are the same as in 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.

 


Tasks

  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 of 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 question 5 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 x 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 x 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 which 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 toward its midpoint. The contracting continues until the length of the line is 0 (zero). Each timer event should then cause the line to become longer on each end; it 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 using the FileQuery class that views a file whose name is selected by the user. 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 which is then made viewable.
  11. File Viewer 2: Write a program using the FileChooser class that views a file whose name is selected by the user. 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, which is then made viewable.
  12. File Viewer 3: Write a program using the FileNavigator class that views a file whose name is selected by the user. 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, which is then made viewable.
  13. Write a program using the FileChooser class that edits a file whose name is selected by the user.
  14. Write a program using the FileNavigator class that edits a file whose name is selected by the user.
  15. Write a program using the FileNavigator class that deletes a file whose name is selected by the user.



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

Legal Statement

 

 

 

ÿ