Outline
Other Material
In this chapter we will examine the technologies that make software frameworks possible.
The solution comes from the two ways we have been using inheritance from the beginning of the book.
A simple example might be a framework for user interfaces:
The foundation method are applicable to any type of window. The deferred specialization methods are appropriate only to one type of application.
By working with deferred methods, the application class views the application in one way, and the child class in another.
(picture got cut off, sorry).
Software frameworks provide a different type of reuse:
Suppose we want to sort employee records. We could write the following.
class Employee { public: string name; int salary; int startingYear; } | void sort (Employee * data[ ], int n) { for (int i = 1; i < n; i++) { int j = i-1; while (j >= 0 && v[j+1]->startingYear < v[j]->startingYear) { // swap elements Employee * temp = v[j]; v[j] = v[j+1]; v[j+1] = temp; j = j - 1; } } } |
But what happens if we want to change it?
We can reuse the idea of a merge sort, but cannot reuse the binary without modifications to the original source code.
To create an object-oriented software framework, we must first ask ourselves what are the likely sources of change?
class InsertionSorter { public: void sort () { int n = size(); for (int i = 1; i < n; i++) { int j = i - 1; while (j >= 0 && lessThan(j+1, j)) { swap(j, j+1); j = j - 1; } } } private: virtual int size() = 0; // abstract methods virtual boolean lessThan(int i, int j) = 0; virtual void swap(int i, int j) = 0; }
The part that is common in made into a foundation method, the part that changes are made into deferred methods.
To apply the framework to a new problem, we subclass and override the deferred methods:
class EmployeeSorter : public InsertionSorter { public: EmployeeSorter (Employee * d[], int n) { data = d; sze = n; } private: Employee * data[]; int sze = n; virtual int size () { return sze; } virtual bool lessThan (int i, int j) { return data[i]->startingYear < data[j]->startingYear; } virtual void swap (int i, int j) { Employee * temp = v[i]; v[i] = v[j]; v[j] = temp; } }
We can now reuse the high level algorithm without making any change to the original source code!
I don't want to give the impression that a framework is always just one class.
Often, a framework is a collection of many classes. For example, a typical GUI framework might have
All can be specialized by the combination of foundation methods for overall structure, and deferred methods for specialization.
A framework can be tremendously helpful in allowing a programmer to rapidly create new application, but only when the application fits into the general structure envisioned by the creator of the framework.
If an application falls outside that framework, then it can be very difficult to overcome the framework.
For example, if the designer of the framework has not encapsulated the right sources of variation in a method, or has forgotten to declare a method as virtual, then it can be very difficult to work with.
The Java Applet API is one simple example of a software framework.
init() | Invoked whe the applet is initialized |
start() | Invoked th start the application |
paint(Graphics) | Invoked to repaint the window |
stop() | Invoked when the applet is halted |
destroy | Invoked when the applet is terminated |
Lots of other classes for constructing buttons and menus, and so on.
Here is a framework like we might use in Chapter 7 (the billiards game).
GraphicalObject = object (* data fields *) link : GraphicalObject; region : Rect; (* initialization function *) procedure setRegion (left, top, right, bottom : integer); (* operations that graphical objects perform *) procedure draw; procedure update; function intersect (anObj : GraphicalObject) : boolean; procedure hitBy (anObj : GraphicalObject); end;
A generalized discrete event-driven simulation can be formed based around the class Event:
class Event { public: Event (unsigned int t) : time(t) { } const unsigned int time; virtual void processEvent () = 0; }; class eventComparison { public: bool operator () (event * left, event * right) { return left->time > right->time; } };
An event is an action that will take place at a specific time.
Discrete event driven simulations were the type of application that helped drive the design of the first object-oriented programming language, Simula. (Early 1960's).
class Simulation { public: Simulation () : eventQueue(), currentTime(0) { } void scheduleEvent (event * newEvent) { eventQueue.push (newEvent); } void run(); unsigned int currentTime; protected: priority_queue<vector, eventComparison> eventQueue; }; void Simulation::run() { // execute events until event queue becomes empty while (! eventQueue.empty()) { event * nextEvent = eventQueue.top(); eventQueue.pop(); time = nextEvent->time; nextEvent->processEvent(); delete nextEvent; } }
The book continues with the development of a simulation based on this framework.
A framework is a way of organizing classes so as to solve a class of related problems