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 };
These new class can be used as follows:
Location nearTop(20, 20), nearCenter(500, 500); Shape smallSquare(50, 50); Shape largeSquare(500, 500);
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 };
Frame objects can be created as follows:
Frame smallTop (nearTop, smallSquare); Frame largeCenter (nearCenter, largeSquare); Frame someWhere (largeSquare); Frame someSize (nearCenter); Frame anyKind;
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.
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.
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.
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.
|
Exercises |