This particular page contains material from one of the final chapters. It uses a simplified "framework class library" that is intended to introduce students to the ideas of reusable designs.
Although OO programming techniques were originally viewed as primarily applicable to simulations, they have over the last ten years become much more widely utilised. This greater use is largely a consequence of the increased opportunity for "reuse" that OO techniques bring to application development.
Reuse, whether of functions, components, or partial designs, always enhances productivity. If you can exploit reusable parts to handle "standard" aspects of an application, you can focus your efforts on the unique aspects of the new program. You will produce a better program, and you will get it working sooner than if you have to implement everything from scratch.
Reusable functions are helpful in the case where all you need to do is calculate something. But if you want to build an interactive program with windows and menus etc, you soon discover problems.
There are function libraries that are intended to be used when building such programs. Multi-volume reference manuals exist to describe them. For example, the Xlib reference manuals define the functions you can used to work with the X-windows interface on Unix. The series "Inside Macintosh" describes how to create windows and menus for the Mac. OS. These books include the declarations of literally hundreds of functions, and dozens of data structures. But these function libraries are very difficult to use.
The functions defined in these large libraries are disjoint, scattered, inconsistently named. There is no coherence. It is almost impossible to get a clear picture of how to organize a program. Instead you are faced with an arbitrary collection of functions, and the declarations of some types of structures that you have to have as globals. Programs built using just these function libraries acquire considerable entropy (chaotic structure). Each function call takes you off to some other arbitrary piece of code that rapes and pillages the global data structures. Reusable components
Program design is different. You start by identifying the individual objects that are responsible for particular parts of the overall data. You define their classes. Often, you will find that you can reuse standard classes, like the collection classes in Chapters 21 and 24. As well as providing working code, these classes give you a way of structuring the overall program. The program becomes a sequence of interactions between objects that are instances of standard and application specific classes.
Essentially, the unit of reuse has become larger. Programs are built at least in part from reusable components. These reusable components include collection classes and, on Unix, various forms of "widget". (A widget is essentially a class that defines a "user interface" component like a menu or an alert box.) Reusable patterns of object interactions and program designs
You launch a program. Once it starts, it presents you with some form of "file dialog" that allows you to create a new file, or open an existing file. The file is opened. One or more windows are created. If the file existed previously, some portions of its current contents get displayed in these new windows. The system's menu bar gets changed, or additional menus or tool bars appear associated with the new window(s). You use the mouse pointer and buttons to select a menu option and a new "tools palette" window appears alongside the document window. You select a tool from the palette. You use the tool to add data to the document.
The behaviour is exactly the same. It doesn't matter whether it is a drawing program or spreadsheet. The same patterns of behaviour are repeated.
Object oriented programming techniques provide a way of capturing common patterns of behaviour. These patterns involve standardized interactions between instances of different classes.
An "opening sequence" pattern could specify that the "application" object handle the initial File/New or File/Open request. It should handle such a request by creating a document object and giving it the filename as an argument to an "OpenNew()" or "OpenOld()" member function. In "OpenOld()", the document object would have to create some objects to store the data from the file and arrange to read the existing data. Once the "open" step is complete, the application object would tell the new document object to create its display structure. This step would result in the creation of various windows.
Much is standard. The standard interactions among the objects can be defined in code:
Application::HandleCommand( command#, ...) Default implementation defined switch(command#) newCommand: doc = this->DoMakeDocument(); doc->OpenNew(); doc->CreateDisplay(); break; openCommand: filename = this->PoseFileDialog(); doc = this->DoMakeDocument(); doc->OpenOld(filename, ...); doc->CreateDisplay(); break; ... Default implementation defined Document::OpenOld(filename, ...) this->DoMakeDataStructures() this->DoRead(filename, ...) Pure abstract functions, implementation is application specific Document::DoMakeDataStructures() ? Document::DoRead(...) ?
Of course, each different program does things differently. The spreadsheet and drawing programs have to create different kinds of data structure and then have to read differently formatted files of data.
This is where inheritance comes in.
The situation is very much like that in the dungeon game with class Monster and its subclasses. The Dungeon code was written in terms of interactions between the Dungeon object and instances of class Monster. But there were never any Monster objects. Class Monster was an abstraction that defined a few standard behaviours, some with default implementations and some with no implementation. When the program ran, there were instances of specialized subclasses of class Monster; subclasses that owned their own unique data and provided effective implementations of the behaviours declared in class Monster.
Now, class Document is an abstraction. It defines something that can be asked to open new or old files, create displays and so forth. All kinds of document exhibit such behaviours; each different kind does things slightly differently.
Specialized subclasses of class Document can be defined. A SpreadSheetDoc would be a document that owns an array of Cell objects where each Cell is something that holds either a text label, or a number, or a formula. A DrawDoc would be a document that owns a list of PictureElements. Each of these specialized subclasses would provide effective definitions for the empty Document::DoRead() and Document::DoMakeDataStructures() functions (and for many other functions as well!).
A particular program won't create different kinds of document! Instead, you build the "spreadsheet" program or the "draw" program.
For the "draw" program, you would start by creating class DrawApp a minor specialization of class Application. The only thing that DrawApp does differently is that its version of the DoMakeDocument() function creates a DrawDoc. A DrawDoc is pretty much like an ordinary Document, but it has an extra List data member (to store its PictureElements) and, as already noted, it provides effective implementations of functions like DoRead().
Such a program gets built with much of its basic structure defined in terms of classes that are specializations of standardized, reusable classes taken from a library. These reusable classes are the things like Application, Document, and Window. Some of their member functions are defined with the necessary code in the implementation files. Other member functions may have empty (do nothing) implementations. Still other member functions are pure virtual functions that must be given definitions in subclasses.
Design ideas are embedded in the code of those functions that are defined in the library. Thus, the "standard opening sequence" pattern implements a particular design idea as to how programs should start up and allow their users to select the data files that are to be manipulated. Another defined pattern of interactions might specify how an Application object was to handle a "Quit" command (it should first give any open document a chance to save changes, tell the document to close its windows and get rid of data, delete the document, close any application windows e.g. floating tool palettes, and finally quit).
The code given for the standard classes will embody a particular "look and feel" as might be required for all applications running on a particular type of machine. The specifications for a new application would normally require compliance with "standard look and feel". If you had to implement a program from scratch, you would have to sort out things like the "standard opening sequence" and "standard quit" behaviours and implement all the code. If you have a class library that embodies the design, you simply inherit it and get on with the new application specific coding.
It is increasingly common for commercial products to be built using standardized framework class libraries. A "framework class library" has the classes that provide the basic structure, the framework, for all applications that comply with a standardized design. The Integrated Development Environment that you have on your personal computers includes such a class library. You will eventually get to use that library.
While real frameworks allow for many different kinds of data and document; this "RecordFile" framework is much more restricted. Real frameworks allow for multiple documents and windows; here you make do with just one of each. Real frameworks allow you to change the focus of activity arbitrarily so one moment you can be entering data, the next moment you can be printing some graphic representation of the data. Here, the flow of control is much more predefined. All these restrictions are needed to make the example feasible. (The restrictions on flow of control are the greatest simplifying factor.)
Figure 30.1 shows the form of the record used in a program, "StudentMarks", built using the framework. This program keeps track of students and their marks in a particular subject. Students' have unique identifier numbers, e.g. the student number 938654. The data maintained include the student's name and the marks for assignments and exams. The name is displayed in an editable text field; the marks are in editable number entry fields. When a mark is changed, the record updates the student's total mark (which is displayed in a non-editable field.)
When the "StudentMarks" program is started, it first presents the user with a menu offering the choices of "New (file)", "Open (existing file)", or "Quit". If the user selects "New" or "Open", a "file-dialog" is used to prompt for the name of the file.
Once a file has been selected, the display changes, see Figure 30.2. It now displays details of the name of the file currently being processed, details of the number of records in the file, and a menu offering various options for adding, deleting, or modifying records.
If the user selects "New record", the program responds with a dialog that requires entry of a new unique record identifier. The program verifies that the number entered by the user does not correspond to the identifier of any existing record. If the identifier is unique, the program changes to a record display, like that shown in Figure 30.1, with all editable fields filled with suitable default values.
If the user picks "Delete record" or "View/edit record", the program's first response is to present a dialog asking for the identifier number of an existing record. The number entered is checked; if it does not correspond to an existing record, no further action is taken.
A "Delete record" command with a valid record identifier results in the deletion of that record from the collection. A "View/edit" command leads to a record display showing the current contents of the fields for the record.
After performing any necessary updates, a "Close" command closes the existing file. The program then again displays its original menu with the options "New", "Open", and "Quit".
"Loans" is another program built on the same framework. It is very similar in behaviour, but this program keeps track of movies that a customer has on loan from a small video store. Its record is shown in Figure 30.3.
The overall behaviours of the two programs are identical. It is just the records that change. With the StudentMarks program, the user is entering marks for different pieces of work. In the Loans program, the user enters the names of movies and rental charges.
Most IDEs can produce hierarchy diagrams, like that in Figure 30.4, from the code of a program. Such a diagram is generated by a "class browser". A specific class can be selected, using the mouse, and then menu commands (or other controls) can be used to open the file with the class declaration or that with the definition of a member function chosen from a displayed list. A class declaration or function definition can be edited once it has been displayed. As you get more deeply into the use of classes, you may find the "browser" provides a more convenient editing environment than the normal editor provided by the IDE.
As illustrated in Figure 30.4, a program built using the framework may need to define as few as three classes. These are shown in Figure 30.4 as the classes MyApp, MyDoc, and MyRec; they are specializations of the framework classes Application, Document, and Record.
Class Record adds a number of additional behaviours. Record objects have to be displayed in windows. The exact form of the window depends on the specific kind of Record. So a Record had better be able to build its own display window, slotting in the various "EditText" and "EditNum" subwindows that it needs. Since the contents of the "edit" windows can get changed, a Record had better be capable of setting the current value of a data member in the corresponding edit window, and later reading back a changed value. Some of these additional functions will be pure virtual, but others may have "partial definitions". For example, every Record should display its record identifier. The code to add the record identifier to the display window can be coded as Record::AddFieldsToWindow(). A specialized implementation of AddFieldsToWindow(), as defined for a subclass, can invoke this standard behaviour before adding its own unique "edit" subwindows.
Every specialized program built using the framework will define its own "MyRec" class. (This should have a more appropriate name such as StudentRec or LoanRec.) The "MyRec" class will define the data members. So for example, class StudentRec would specify a character array to hold the student's name and six integer data members to hold the marks (the total can be recomputed when needed). A LoanRec might have an array of fixed length strings for the names of the movies on loan.
The specialized MyRec class usually wouldn't need to add any extra functionality but it would have to define effective implementations for all those pure virtual functions, like DiskSize() and ReadFrom(), declared in class KeyedStorable Item. It would also have to provide the rest of the implementation of functions like Record::AddFieldsToWindow().
A program that has to work with a small number of records might chose to hold them all in main memory. It would use a simple collection class like a dynamic array, list, or (somewhat better) something like a binary tree or AVL tree. It would work by loading all its records from file into memory when a file was opened, letting the user change these records and add new records, and finally write all records back to the file.
A program that needed a much larger collection of records would use something like a BTree to store them. Collection c
The actual "collection classes" are just those introduced in earlier chapters. The examples in this chapter use class DynamicArray and class BTree, but any of the other standard collection classes might be used. An instance of the chosen collection class will hold the different Record objects. Figure 30.4 includes class DynamicArray, class BTree and its auxiliary class BTreeNode.
The different collection classes have slightly different interfaces and behaviours. For example, class BTree looks after its own files. A simpler collection based on an in-memory list or dynamic array will need some additional component to look after disk transfers. But we don't want such differences pervading the main code of the framework.
Consequently, the framework uses some "adapter" classes. Most of the framework code can work in terms of a "generic" Collection that responds to requests like "append record", "delete record". Specialized "adapter" classes can convert such requests into the exact forms required by the specific type of collection class that is used.
Figure 30.4 shows two adapter classes: ADCollection and BTCollection. An ADCollection objection contains a dynamic array; a BTCollection owns a BTree object (i.e. it has a BTree* data member, the BTree object is created and deleted by code in BTCollection). These classes provide implementations of the pure virtual functions Collection::Append() etc. These implementations call the approp-riate functions of the actual collection class object that is used to store the data records. The adapter classes also add any extra functions that may be needed in association with a specific type of collection.
A CommandHandler is something that has the following two primary behaviours. Firstly it "runs". "Running" means that it builds a menu, loops handling commands entered via the menu, and finally tidies up.
void CommandHandler::Run() { this->MakeMenu(); ... this->CommandLoop(); ... this->Finish(); }CommandHandler
The second common behaviour is embodied in the CommandLoop() function. This will involve menu display, and then processing of a selected menu command:
void CommandHandler::CommandLoop() { while(!fFinished) { ... int c = pose menu dialog ... this->HandleCommand(c); } }A CommandHandler object will continue in its command handling loop until a flag, fFinished, gets set. The fFinished flag of an Application object will get set by a "Quit" command. A Document object finishes in response to a "Close" command.
As explained in the previous section, the Application object will have a menu with the choices "New", "Open" and "Quit". Its HandleCommand() function is:
void Application::HandleCommand(int cmdnum) { switch(cmdnum) { case cNEW: fDoc = this->DoMakeDocument(); fDoc->DoInitialState(); fDoc->OpenNew(); fDoc->Run(); delete fDoc; break; case cOPEN: fDoc = this->DoMakeDocument(); fDoc->DoInitialState(); fDoc->OpenOld(); fDoc->Run(); delete fDoc; break; case cQUIT: fFinished = 1; break; } }The "New" and "Open" commands result in the creation of some kind of Document object (obviously, this will be an instance of a specific concrete subclass of class Document). Once this Document object has been created, it will be told to open a new or an existing file, and then it will be told to "run".
The Document object will continue to "run" until it gets a "Close" command. It will then tidy up. Finally, the Document::Run() function, invoked via fDoc ->Run(), will return. The Application object can then delete the Document object, and resume its "run" behaviour by again displaying its menu.
How do different applications vary?
The application objects in the "StudentMarks" program and "Loans" program differ only in the kind of Document that they create. A "MyApp" specialized subclass of class Application need only provide an implementation for the DoMakeDocument() function. Function Application::DoMakeDocument() will be "pure virtual", subclasses must provide an implementation. A typical implementation will be along the following lines:
Document *MyApp::DoMakeDocument() { return new MyDoc; }A StudentMarkApp would create a StudentMarkDoc while a LoanApp would create a LoanDoc.
Specialized subclasses of class Application could change other behaviours because all the member functions of class Application will be virtual. But in most cases only the DoMakeDocument() function would need to be defined.
Class Document is substantially more complex than class Application. It shares the same "run" behaviour, as defined by CommandHandler::Run(), and has a rather similar HandleCommand() function:
void Document::HandleCommand(int cmdnum) { switch(cmdnum) { case cNEWREC: DoNewRecord(); break; case cDELREC: DoDeleteRecord(); break; case cVIEW: DoViewEditRecord(); break; case cCLOSE: DoCloseDoc(); fFinished = 1; break; } }Functions like DoNewRecord() are implemented in terms of a pure virtual DoMakeRecord() function. It is this Document::DoMakeRecord() function that gets defined in specialized subclasses so as to create the appropriate kind of Record object (e.g. a StudentRec or a LoanRec).
Document objects are responsible for several other activities. They must create the collection class object that they work with. They must put up dialogs to get file names and they may need to perform other actions such as getting and checking record numbers.
While the "adapter" classes can hide most of the differences between different kind of collection, some things cannot be hidden. As noted in the discussion above on ADCollection and BTCollection, there are substantial differences between those collections that are loaded entirely into memory from file as a program starts and those, like the BTree based collection, where individual records are fetched as needed.
There has to be a kind of parallel hierarchy between specialized collection classes and specialized Document classes. This is shown in Figure 30.4 with the classes ArrayDoc and BTDoc. An ArrayDoc object creates an instance of an ADCollection as its Collection object while a BTDoc creates a BTCollection. Apart from DoMakeCollection() (the function that makes the Collection object), these different specialized subclasses of Document differ in their implementations of the functions that deal with opening and closing of files.
Different programs built using the framework must provide their own specialized Document classes - class StudentMarkDoc for the StudentMarks program or LoanDoc for the Loans program. Figure 30.4 uses class MyDoc to represent the specialized Document subclass needed in a specific program.
Class MyDoc won't be an immediate subclass of class Document, instead it will be based on a specific storage implementation like ArrayDoc or BTDoc.
The basic Window class is an extended version of that used in Chapter 29. It possesses the same behaviours of knowing its size and location on the screen, maintaining "foreground" and "background" images, setting characters in these images etc. In addition, these Window objects can own "subwindows" and can arrange that these subwindows get displayed. They can also deal with display of text strings and numbers at specific locations.
Class NumberItem is a minor reworking of the version from Chapter 29. An instance of class NumberItem can be used to display the current value of a variable and can be updated as the variable is changed.
The simple EditText class of Chapter 29 has been replaced by an entire hierarchy. The new base class is EditWindow. EditWindow objects are things that can be told to "handle input". "Handling input" involves accepting and processing input characters until a '\n' character is entered.
Class EditNum and EditText are simple specializations that can be used for verified numeric or text string input. An EditNum object accepts numeric characters, using them to determine the (integer) value input. An EditNum object can be told the range permitted for input data; normally it will verify that the input value is in this range (substituting the original value if an out of range value is input). An EditText object accepts printable characters and builds up a string (with a fixed maximum length).
A MenuWindow allows a user to pick from a displayed list of menu items. A MenuWindow is built up by adding "menu items" (these have a string and a numeric identifier). When a MenuWindow is displayed, it shows its menu items along with an indicator of "the currently selected item" (starting at the first item in the menu). A MenuWindow handles "tab" characters (other characters are ignored). A "tab" changes the currently selected item. The selection moves cyclically through the list of items. When "enter" ('\n') is input, the MenuWindow returns the numeric identifier associated with the currently selected menu item.
The "dialogs" display small windows centered in the screen that contain a prompt and an editable field (an instance of class EditNum or class EditText). The user must enter an acceptable value before the dialog will disappear and the program continue. Class InputFileDialog is a minor specialization of TextDialog that can check whether a string given as input corresponds to the name of an existing file.
Class RecordWindow is a slightly more elaborate version of class MenuWindow. A RecordWindow owns a list of EditNum and EditText subwindows. "Tabbing" in a RecordWindow selects successive subwindows for further input.
The "MyRec" class used in a particular program implements a function, AddFieldsToWindow(), that populates a RecordWindow with the necessary EditNum and EditText subwindows.