[N.A.B.G. picture]
This page contains a draft of a paper on teaching Object Oriented Programming; the final paper will be presented at the Australian Software Engineering Conference in Melbourne in July 1996.

Teaching Object Orientation: Patterns and Reuse


Abstract

This paper describes the content of an advanced undergraduate "Object Oriented Programming" subject that has the aim of developing good Software Engineering practices with regard to reusable designs and reusable components. The subject utilizes framework class libraries. A simplified framework, embodying many of the design patterns from more complex frameworks, is used to provide an introduction to frameworks. Assignments for the subject give students practice in reusing components (such as collection classes), standard design patterns (such as command handler chains), and complete designs for applications as provided by the frameworks.

Introduction

Why has Object Oriented (OO) Programming gained acceptance?

The primary reason is that OO represents another step in the progression towards higher levels of design reuse.

Function libraries allow you to reuse algorithms. You don't write a sine function, you use sin() from the math library. You don't write a sort routine, you use qsort() from stdlib. Design by top down functional decomposition allows you to consider the overall role of a program and then break it down successively until you have functions that either exist in a library or that you can write. You build up a program from a set of standard library functions and special purpose functions that generally operate on shared global data. Reusing the code for standard functions is better rewriting the code (and is a lot better than reinventing algorithms) but this style offers little more than minimal code reuse.

Component class libraries and object based programs allow reuse at a larger level of granularity. You reuse complete abstract data types that package data with the functions that are to manipulate those data. You don't have to implement code to interrogate the operating system and build up a representation of today's date, you use an instance of class Date from the class library. You don't implement your own keyed storage structure, you use an instance of class AVL or class BTree from some library. Your approach to design changes. Your first step is to identify a partitioning of the overall program system into separate components. You then identify their classes, determining what data each instance of a class must own, and what these objects do. Some of the classes will turn out to be reusable components from a class library. Finally, you build up your program as a unique system of interacting objects.

Reuse of prefabricated components like lists and trees does enhance productivity and, usually, improves reliability. The major gain tends to be from the more disciplined approach to design. The extent of reuse is limited. A reusable class List is preferable to a newly re-implemented version, but it is only in first year computing science assignments that a List represents an important aspect of a programming project.

Inheritance is the feature that distinguishes an object oriented from an object based style. Inheritance is a way of representing and exploiting similarities (or, if you want view things the other way, inheritance allows you to represent and exploit difference). OO programming styles originated with simulations where it is natural to have an abstraction, e.g. class Aircraft, that represents the similar behaviours of a variety of specialized forms such as Jets, Helicopters, HangGliders etc. A simulation program can work with a collection of Aircraft, sending each messages such as "climb", "turn", or "land" and relying on each specialized type responding to the request in an appropriate manner.

Many texts and courses limit their illustrations of OO to simple class inheritance. Thus, you see examples such as class Shape with the specializations Triangle, Square, and Circle, or class VendingMachine with specializations CandyMachine and DrinksMachine, or a class library where inheritance is used to express relationships among different collection classes (e.g. the NIH class library [Gorlen] or Bergin's data abstraction library [Bergin]).

Such simple class hierarchies provide examples of how commonalities can be exploited. Thus, the different subclasses of class VendingMachine may share the base class's member function that calculates change. Polymorphism and dynamic binding can also be illustrated; thus, one can have a list of Shape* pointers and a function that works along the list telling each Shape to draw itself and compute its area.

These hierarchies can also show how code can be made more general, and how it is possible to avoid premature design decisions. For example, a unified collection class hierarchy allows a program to be written relying on the use of an instance of an abstract Collection class, while deferring decisions as to the particular concrete implementation.

Such uses of inheritance can improve design and reuse to a small degree. However, the classes in these hierarchies are very much stand alone. They have no predefined relationships with other classes. As such, they fail to take full advantage of the OO approach.

OO allows exploitation of similarities. This need not be limited to similarities in specific behaviours of objects that are instances of related classes. Many programs have substantial similarities in their overall operation.

Just think about the way the different word processor, spreadsheet, diagram drawing and other programs work on your personal computer. They all have the same arrangements of "window", "menus", "undo/redo" editing operations and so forth. Obviously, they all have a lot of rather similar code. The data drawn in the windows, and the specific menu commands differ, but all the code for picking up a mouse click, recognizing it as a menu selection, finding which menu option must be similar in all the programs.

If you abstract out this similar code, you can greatly decrease the cost of developing new applications. The new applications can reuse not just individual classes, but clusters of classes whose patterns of interactions are already defined at least in part.

Much of the power of the object-oriented approach derives from the ability to express predefined patterns of interactions among instances of different classes in a "class cluster". The class hierarchies of the classes involved in such clusters again reflect similarities among related components; but the class cluster itself captures something rather greater. A cluster captures similarities among applications (programs).

The patterns of interactions among the classes of a cluster can represent a relatively complex behaviour. Such a pattern may constitute a general solution to a common problem. For example, the "Chain of Command" pattern [Gamma] is a standard mechanism for identifying the specific object that should handle a given user input in an interactive program. Alternatively, a pattern may define a behaviour required by the "look and feel" guidelines for applications developed on a given platform. For example, when an Application object is told to "quit", it may be required to first give each open Document object a chance to save any altered memory-resident data to their corresponding files.

These clusters contain a number "partially implemented abstract classes". "Partial implementation" describes the way that standard behaviours are already encoded in member functions of the class cluster. The member functions for one class will invoke behaviours of instances of other classes in the cluster. Thus, Application::DoQuit() might include code of the form "for document in document_list do document->AboutToQuit()", with the function Document::AboutToQuit() containing code like "if(this ->Changed() && gDialogManager->SaveChangesDialog()) this->DoSave()". Although such code defines the overall behaviour, vital bits are missing; thus Document::DoSave() may be a pure virtual function or have simply an empty body. It represents a function that must be replaced in any concrete class derived from the cluster's Document class.

The clusters may also include some concrete classes. For example, the cluster might include "dialogs" with "text item" and "number item" objects that can accept keyed input. Like collection classes such as lists, these user interaction elements can be used as "off the shelf" components. Developers don't need to extend these classes; instances can be instantiated and used as required.

Such class clusters raise "reuse" to a new level. A cluster embodies a completed design for a particular aspect of an application. For example, a "command handler" cluster with classes Application, Document, Window etc is an implementation of a particular design for a program, any program, that has documents (associated with disk files) whose contents are to be displayed in windows. Programs created using the cluster will respond in the "standard way" to all standard commands like File/New, File/Open, File/Quit. In some cases, the code of the class cluster can actually be compiled and linked and will run, working "correctly", albeit with blank windows and empty files.

When the basics of launching a program, opening file(s), and creating windows are handled by the standard code, developers can focus more on those aspects that make their program unique. Their program has specific data structures that are owned by the documents and are transferred between memory and disk files. So they must define class OurDoc : public Document with extra data members to store the problem specific data and they must provide effective implementations of functions such as OurDoc::DoSave() and OurDoc::DoRestore(). Similarly, the developers will have to provide effective implementations for the function(s) that display some representation of their data in a window.

Part of the work of the developers of a new program involves the definition of new classes that inherit from partially implemented abstract classes defined in a "framework" class library. This work involves essentially no new design. The framework classes provide the basic reusable design; the developers need only provide implementations of a few specific routines.

The rest of the work, i.e. the application specific aspects, will involve design studies. The developers must identify the data that are to be represented and must determine how a user may create, subsequently modify, and eventually use the data.

Unlike the reuse of component, e.g. a collection class such as a list, the reuse of an elaborate class cluster or framework can make a substantial difference to developers' productivities. Programs built using frameworks are delivered faster and are much more likely to comply with "look and feel" guidelines than those developed independently.

It is this style of usage that represents the real power of the OO approach. An OO programming course should lead into the use of recognized design patterns [Gamma] and reusable frameworks [Lewis].

Course structure

The OO programming course described here is a final year elective in the undergraduate program. The course originated in 1988 and has been evolving steadily. Currently, the C++ language is used. The normal programming background for enrolled students has been one year's experience with C (with minor use of C++), following an initial year using Modula2.

The students have already studied abstract data types, implemented using Modula2 and C modules and also as simple C++ classes. The OO programming course starts by revising the use of simple C++ classes as a mechanism for implementing abstract data types and by covering genericity (C++ templates etc).

The "oriented" part of the course starts with simulation examples. A standard example uses a Moria (Rogue) like game where the player seeks to explore some maze subject to some resistance from the maze's inhabitants. The maze is inhabited by numerous creatures drawn from different races. All creatures have essentially the same behaviour (they seek to end the game by eliminating the human player), but each different kind (class) of creature goes about this task in a distinct way. The game program works with a collection of creatures. At appropriate times, the creatures get their chance to proceed Ð the code is simple "for each creature in creature list do creature.run". Here the reference variable creature is polymorphic, as we move along the list the variable creature may reference first a ghost, then a troll, etc.

The assignments performed at this stage by students are similar but more prosaic. Their programs simulate things like "activities" in a supermarket (with activity subclasses including things such as checkouts and customers). Such simulation examples provide an easy introduction to the use of class hierarchies, heterogeneous collections, and polymorphic pointers as well as an opportunity to reuse standard components such as lists. The nature of the examples is such that there is rarely any difficulty in recognizing the objects and their classes during the design process.

The main aim of the course is however to get the students familiar with the reuse of designs and frameworks. This objective is similar to that of Huni and Metz with their "Games Factory" [Hunni] but here the examples are much more closely tied to the intended subsequent work with full scale frameworks [Lewis].

The standard Graphics User Interaction (GUI) frameworks are used. These GUI frameworks include: for Intel architecture, Borland's Object Windows Library (OWL) [OWL], and Microsoft's Foundation classes (MFC) [MFC]; for Macintosh: Symantec's Think Class Library (TCL2) [TCL] and Apple's MacApp [Apple]; while for Unix there are analogues like ET++. [Weinand]

However, there are some problems related to the introductory use of such frameworks. They are all relatively "mature" systems intended for commercial product development, and consequently they are elaborate with literally hundreds of classes. Some frameworks are poorly documented. Consequently, the frameworks are somewhat intimidating on first encounter. Despite these complexities and other problems, use of the frameworks is possible, provided that there is a suitable introduction.

The RecordFile framework class library described in the subsequent sections has been introduced to serve as a simple generic introduction to the GUI frameworks. It embodies much the same conceptual model for a program, though in a considerably more restricted and hence simpler form. The framework is used in an introductory exercise and assignment to illustrate general principles before students start work using one of the main GUI frameworks. This introductory assignment gets students to create new classes that inherit from "command handler" and other classes provided by the framework as well as using concrete components like "file dialogs".

The final part of the course involves two small assignments illustrating the use of a standard GUI framework class library and the development by each student of their own framework-based application. The first of these assignment will usually involve just input and editing of data using standard concrete class components (e.g. a "number input" object); the second assignment is generally some variation on a graphic editor. Details of the independent project are given later.

The RecordFile Framework

Frameworks are designed to provide a basic structure for solving problems in a particular restricted domain. The problem domain for the RecordFile framework is one that is familiar to both students and their instructors. It is the domain of "file of records".

Typical first year programming courses introduce "records" (as Pascal/Modula2 records or C structs) toward the end of the first semester or at the start of the second semester. Shortly afterwards, files of records (either sequential or random access files) are added. Favoured assignments have the students writing programs that work with "student records" (name, subject, several assignment marks), or "customer records" (name, address, item on order, date), or "loan records" (name, names of movies/books on loan). Records have unique identifier keys such as a "student number", or "customer identifier". The program must load and display existing records, permit changes to existing records and allow for the addition (and possibly deletion) of records.

Such programs have very similar structures. In fact, the only things that really differ are the record and the form of the "display" used to show a record's content and to permit changes to fields. The commonalities can be captured in a simple framework.

The classes in the framework provided to students are summarized in Figure 1. Unlike some of the older frameworks (MacApp, MFC, ET++), these classes do not share a single root like class Object. Instead, there are a number of separate hierarchies, only the "windows hierarchy" has any real complexity.
[Fig.1]

Figure 1 Classes in the RecordFile framework.

The classes include a "record" class, some standard collection classes, simplified Application and Document classes modelled on those in the standard GUI frameworks, and a set of "windows" classes. The framework uses a simple cursor-addressable ("curses") style of window display. Class WindowRep is a "wrapper" class that hides the low-level operating systems dependent aspects of cursor-addressing; it relies on a common subset of the "curses"-like mechanisms that exist on Unix, PC, and Macintosh (Symantec) systems. An example display, from a Macintosh implementation, is shown in Figure 2.
[Fig.1]

Figure 2 Example record display.

A program can be built on the framework by providing specialized versions of the classes Record, Application, and Document. (The specialized "MyDoc" class will be derived from a framework provided subclass of class Document. Figure 1 shows classes BTDoc and ArrayDoc. As explained below, these specialized document classes differ in the way they hold records.)

The basic behaviour of a program built using the RecordFile framework is as follows:

Those familiar with the GUI frameworks will have observed that the flow of control is far more constrained than is typical. This is probably the major simplification in this framework's code.

Document object's can work with memory resident collections or disk based collections of records. The memory resident versions can employ simple lists or dynamic arrays to hold the records, or may use more appropriate structures such as an instance of an AVL-tree class. The disk based collections use an instance of a BTree class to organize data records in disk files and arrange for loading of records on demand. (The students are familiar with these structures having seen them earlier in an "abstract data types" subject in which the collections were implemented either as C++ classes or C "modules".)

CommandHandlers (i.e. Document and Application objects) work with MenuWindows. Class MenuWindow has a member function, AddMenuItem(char* name, int cmdNumber), that is used to set the options. A MenuWindow handles input, ignoring all characters save "tab" and "enter"; tabs change the current selection, "enter" results in the MenuWindow returning the command number associated with the currently selected menu item.

A Record uses a RecordWindow when interacting with the user. Like a dialog in any of the standard GUI frameworks, a RecordWindow owns a list of subwindows. Some of the subwindows may be only for output, most subwindows will display editable data. A Record creates both its RecordWindow and the subwindows needed; adding the subwindows to the RecordWindow.

A RecordWindow responds to tab characters by changing the editable subwindow responsible for handling subsequent input. An EditText subwindow accepts all printable characters, while an EditNum accepts digits; subwindows relinquish control to their enclosing RecordWindow when given characters like tab or enter. The RecordWindow can ask a subwindow whether its contents have been changed. If the data were changed, the RecordWindow informs its Record, providing details of the subwindow affected.

The Record can update its own data members by asking the subwindow for the new data. Integer identifiers are used to pair subwindows and data members.

Most of the code needed to handle the interactions among objects is provided by the framework classes. For example, a "Document/New record" menu selection is processed using the DoNewRecord() and DoEditRecord() functions:

void Document::DoNewRecord()
{
	long key = GetKey();
	if(key<=0)
		return;
	Record *r = DoMakeRecord(key);
	DoEditRecord(r);
	fStore->Append(r);
	fStore->Save(r);
}
void Document::DoEditRecord(Record *r)
{
	RecordWindow *rw = r->DoMakeRecordWindow();
	rw->PoseModally();
	delete rw;
}

The record is created, is told to make its window, and later is added to the collection. The user interactions are handled in the RecordWindow object's PoseModally() function which invokes actions by the various EditText, EditNum subwindows and the Record involved:

void RecordWindow::PoseModally()
{
	char ch;
	// Initialization
	DisplayWindow();
	CountEditWindows();
	InitEditWindows();
	fCurrent = fNumEdits;
	// Loop until user ends input with "enter'
	do {
		// Activate next edit subwindow, set pointer fEWin
		NextEditWindow();
		fRecord->SetDisplayField(fEWin);
		// Subwindow gets input up to terminator character (returned)
		ch = fEWin->GetInput();
		if(fEWin->ContentChanged()) {
			// Update record
			fRecord->ReadDisplayField(fEWin);
			fRecord->ConsistencyUpdate(fEWin);
			}
	} while(ch != '\n');
}

The framework's Application and Document classes require only minor extension to get a working program e.g.:

class StudentMarkApp : public Application {
protected:
	virtual	Document* DoMakeDocument();
};
class StudentMarkDoc : public BTDoc {
protected:
	virtual Record	*DoMakeRecord(long recnum);
};

The implementation for their member functions is as expected:

Document *StudentMarkApp::DoMakeDocument()
{
	return new MyDoc;
}
Record *StudentMarkDoc::DoMakeRecord(long recnum) 
{
	return new StudentRec (recnum);
}

The specialized subclass of class Record does have some substance, but it is all quite simple, e.g. :

class StudentRec : public Record {
public:
	StudentRec (long recnum);

	// Redefine KeyedStorableItem functions
	virtual void	WriteTo(fstream& out) const;
	virtual void	ReadFrom(fstream& in);
	virtual long	DiskSize(void) const ;
	
	// Redefine Record functions
	virtual void	SetDisplayField(EditWindow *e);
	virtual void	ReadDisplayField(EditWindow *e);
protected:
	virtual void	ConsistencyUpdate(EditWindow *e);
	virtual void	AddFieldsToWindow();
	// Declare unique data members
	char	fStudentName[64];
	long	fMark1;
	...
	long	fTotal;
};

The function AddFieldsToWindow() populates the RecordWindow with the necessary EditText and EditNum editable subwindows:

void StudentRec::AddFieldsToWindow()
{
	Record::AddFieldsToWindow();

	// Adding an EditText
	EditText *et = new EditText(1001, 5, 4, 60,
		"Student Name ");
	fRW->AddSubWindow(et);

	...
}

Functions SetDisplayField() and ReadDisplayField() transfer data to/from the EditText (for the name) and EditNum windows:

void StudentRec::ReadDisplayField(EditWindow *e)
{
	long id = e->Id();
	switch(id) {
	// Reading an EditText
case 1001:
	char* ptr = ((EditText*)e)->GetVal();
	strcpy(fStudentName, ptr);
	break;
	// Reading an EditNum
case 1002:
	fMark1 = ((EditNum*)e)->GetVal();
	break;
...
...
	}
}

The ReadFrom() and WriteTo() functions involve a series of low level read (write) operations that transfer the data for the individual data members of the record:

void StudentRec::ReadFrom(fstream& in)
{
	in.read((char*)&fRecNum, sizeof(fRecNum));
	in.read((char*)&fStudentName, sizeof(fStudentName));
	...
}

The complete code for the framework, and associated examples, can be covered in a two hour lecture. Usually more time would be needed to comment on and elaborate on some of the standard interaction patterns that occur in the code. The standard examples illustrate the use of two different collection classes: an in memory dynamic array and disk based storage using a BTree.

Assignments using the framework involve use of different records, different collection classes and extensions such as "caching" of disk based records.

Patterns illustrated in the RecordFile Framework

The framework code illustrates some of the simpler patterns from reference [Gamma].

For example, although a program may have many windows, they all share the same screen. Access to the screen is controlled by an instance of the "wrapper" class WindowRep. There should only be one WindowRep object in control of the screen. All the different window objects need to access this screen controller object. It has to perform some initialization tasks the first time that it is used. Given these characteristics, it is appropriate to make the WindowRep class a "singleton":

class WindowRep {
public:
	static WindowRep *Instance();
	void	PutCharacter(char ch, int x, int y);
	void	Beep();
	void	Delay(int nseconds);
	...
private:
	WindowRep();
	void	Initialize();
	void 	PutCharacter(char ch);
	static WindowRep *sWindowRep;
	char	fImage[CG_HEIGHT][CG_WIDTH];
	...
};
Code using the WindowRep object follows the standard "singleton" idioms, e.g.:
void InputFileDialog::PoseModally(char *current, 
	char newdata[], int checked)
{
	DisplayWindow();
	for(;;) {
		// Loop until valid file name given
		fE->SetVal(current, 1);
		fE->GetInput();
		strcpy(newdata,fE->GetVal());
		if(!checked)
			return;
		// Try opening file
		ifstream in;
		in.open(newdata,ios::in | ios::nocreate);
		int state = in.good();
		in.close();
		if(state)
			return;
		// Warn of invalid file name
		WindowRep::Instance()->Beep();
		fE->SetVal("File not found", 1);
		WindowRep::Instance()->Delay(1);
		}
}

Several different collection classes were available from an "abstract data types" subject. These included lists, dynamic arrays, binary tree, an AVL tree, and a BTree class. Their class interfaces were not identical. The framework code needed to work with a "collection". Use of the existing classes naturally lead to "adapters". The framework classes, such as BTCollection in Figure 1, illustrate the Adapter pattern from reference [Gamma].

Functions such as Document::DoMakeRecord() and Application::DoMakeDocument() are standard examples of the "Factory Method" pattern. This pattern removes from the framework code all program specific details related to the exact type of record or document that must be created.

The relatively simple and short code of the RecordFile framework makes it easier to illustrate the implementation of these standard patterns and to explain their use.

Use of the GUI frameworks

The final part of the course involves the development by each student of their own framework-based application. The students have to propose an appropriate project, provide a specification, initial documentation, an implementation, and final detailed class documentation. Students chose a specific environment for their two small framework based exercises and their project; the normal choices are Borland's OWL framework for the PC, Symantec's TCL2 for the Macintosh, and ET++ for those who prefer to work on Unix.

The initial documentation is supposed to include scenarios illustrating the objects and interactions involved when the proposed program is handling various aspects of its work. Designs are documented using diagrams and pseudo-code. The design diagrams used include simplified versions of some of the design notations of Booch [Booch] as well as object interaction diagrams like those in reference [Gamma].

Past generations of students have chosen:

Although the majority of the applications developed by the students are very simple, they do give the students sufficient initial experience with the framework environments so that subsequently more ambitious projects can be undertaken. Essentially all projects for Macintosh and PC platforms should nowadays be done at the framework level rather than the much lower "Applications Programmer Interfaces" provided by these OSs.

Conclusions

The course has over the last few years successfully introduced students to the concept of reusable designs such as those embodied in the frameworks. The RecordFile framework has been recently introduced to ease the process of learning to work with a framework and to serve as a specific model of abstract design patterns.

Our overall course structure is changing with object-based design styles now being accorded consisderably more time in the early undergraduate curriculum. In future, students entering the OO programming subject will have substantial experience of using C++ classes for data abstraction and template-style genericity. This will allow the course to focus even more strongly on the "resuse of design patterns".

References


More details on the framework and related examples. Code for framework.
Please email questions to nabg@cs.uow.edu.au