Forsooth! Eight Pattern Hatching columns without so much as one new pattern. Time to make amends.
Many code generation tools (such as user interface builders, parser generators, "wizards," and 4GL compilers) have no problem generating code that's correct and efficient. In fact, people have been known to study computer-generated code in preference to voluminous documentation as a way to learn a new programming interface. But generating functionally complete and maintainable code is another story. Normally you can't create a finished, nontrivial application automatically with one of these tools; you have to implement some functionality the old-fashioned way, by hand, in a programming language. That's because the high-level metaphor that gives the tool its power is rarely expressive enough to let you specify every (not-so-)little detail. The tool's abstractions are necessarily different from those of the programming language. The resulting mixed metaphor can wreak havoc on the application's extensibility and maintainability.
Consider a user interface builder that lets you assemble not only user interface elements like buttons, scroll bars, and menus (AKA "widgets") but also more primitive graphical objects like lines, circles, polygons, and text*. The builder lets you draw pictures with these objects and then associate behavior with them. Hence you can specify more of the application in the tool than would be possible if it provided widgets alone.
You can use such a builder to create the complete user interface for an alarm clock application. You can draw the clock face by assembling lines, polygons, and text as shown in Figure 1. Then you add buttons for setting the current time, the alarm time, and for shutting off the alarm. The builder lets you compose all these elements into a complete user interface for the application.
What the builder doesn't let you specify, however, is how these elements behave when the application runs. Specifically, you need to program the behavior of the buttons and the hour, minute, and second hands. The most basic thing you need to be able to do is refer to these objects in your code. This builder lets you single out objects for "export"---that is, you can give them names with which to refer to them programmatically. In Figure 2, the user is singling out the second hand, an instance of a Line class, for export. The builder pops up a dialog box in response (Figure 3), letting you type in an evocative name like _sec_hand for the Line instance.
The builder can create other elements of the clock's user interface, such as the dialog boxes that let a user specify the current time and the alarm time. But that's about as far as the builder takes you in this application. You still need to specify what the buttons do when they're pressed, how the clock keeps time, and how it maintains the proper appearance. All this behavior must be programmed manually. How do you do it?
In the most straightforward approach, you'd take the code the builder generates and change it to suit your needs. For example, you would add code that generates an event of some sort every second. You'd also write a corresponding event handler that would rotate the second hand and possibly the minute and hour hands by an appropriate amount (-6 degrees for the second hand) every second. (Presumably, lines and other graphical objects have an interface for rotating themselves by a given angle.) Still more new code would implement button behavior. You continue changing the generated code by hand until you wind up with a complete application.
Now for the maintenance problem. Suppose you want to rearrange the user interface so that the buttons appear above the clockface instead of below it. In other words, you want to change just the appearance, not the behavior. Any builder worth its salt will make cosmetic changes like this easy. Trouble is, the builder knows nothing about your modifications to the code it generated earlier. Regenerating the code blindly will clobber your changes or at least force you to reapply them.
There are several approaches to this problem. The builder can mark the code it generates as user-modifiable or not, usually by peppering it with ominous-looking comments warning against modification. But that approach is unsatisfying for at least two reasons:
A more sophisticated approach computes the differences between the modified code and the code generated originally, then attempts to merge those differences into the regenerated code. Needless to say, this is a dicey proposition when the hand modifications are extensive---or sometimes just nontrivial.
An ideal solution would be more reliable than that, and for maintainability's sake would keep generated code separate from hand-programmed modifications. But a strict separation can be hard to achieve, because modifications often need to access parts of the generated code that should not be public. For example, the Line object representing the second hand probably shouldn't be accessible to objects outside the clock, since the line is an implementation artifact. Even a higher-level interface for advancing the second hand probably shouldn't be made public---after all, most real-life clocks have no such capability.
Generation Gap is a pattern that solves this problem through class inheritance. It encapsulates generated code in a class and then splits that class into two, one class for encapsulating generated code and another for encapsulating modifications.
In our clock example, the builder would encapsulate the code it generates in a "core class" named Clock_core. All the code that implements the clock as specified in the builder---including the graphical objects, widgets, and the way they are composed---lives in this class. No one ever instantiates the core class. Rather, the class to instantiate is the Clock class, also known generically as the "core subclass." The builder generates the core subclass along with the core class.
As the generic name "core subclass" suggests, Clock is a subclass of Clock_core. But it's a trivial subclass: it neither adds, removes, nor modifies state or behavior of the core class. It does nothing more or less than its superclass. Nevertheless, code that creates Clock objects always instantiates Clock, the core subclass---not Clock_core, the core class.
So where do modifications go? You could modify the core class to work with other application code, but subsequent editing and regeneration of the interface in the builder would create the dreaded code merging problem described earlier. Instead, you modify the core subclass---never the core class---to add, change, or remove functionality. You can define new member functions, and you can redefine or extend the core class virtual functions. By declaring exported instances as protected member variables, the core class can access them without exposing them to all comers.
If you want to modify the interface's appearance later, then the builder can regenerate only the core class, which you haven't modified; your original changes to the core subclass are thus unaffected. Then you can recompile the application, and it will reflect the change in appearance. Only if you make radical changes to the interface (like removing the second hand or any other instances to which modifications refer) should you have to amend your original changes.
Syntactic incompatibility is usually easier to correct than semantic incompatibility. Both incompatibilities diminish the pattern's effectiveness, so they must not be inherent to the code being generated.
In a file-based programming language and environment, the most secure way to preclude such changes is to place the class declaration and implementation in one or more write-protected files. However, the files cannot be so well protected that the tool can't overwrite them when it regenerates the code. You might need to give the tool special privileges independent of its user's privileges.
Things might be easier in a programming environment that uses some sort of database to store program information. Such environments usually offer finer-grained access control to program source than can be had with conventional file systems.
However, keep in mind that the more information the core class exposes, the more likely it is that regenerating the code will cause something to break in the modified core subclass. So core subclasses should make minimal assumptions about what the core class provides beyond its public interface.
Naming is another important issue. Because the pattern often splits one class into two, the names of the resulting core class and subclass should reflect their origin and close relationship. But the split is immaterial to clients nonetheless. Therefore the core subclass should retain the name of the original class, and the core class should derive its name from its subclass---the opposite of normal. In the builder example, we appended "_core" to the name of the core subclass to produce the core class name.
One last implementation issue concerns the granularity of core class operations. The main benefit of code generation is that it raises the level at which one programs. Often the code generator must produce lots of code to implement a high-level metaphor. As a result, the programmer who wants to modify or extend the generated code will be oblivious to most of its intricacies. How then can we expect him to change it?
The key to this problem lies in the core class interface. The operations must be fine-grained enough that a programmer can override precisely the functionality he's interested in and reuse the rest. If for example the core class implemented everything in one operation, then the programmer wouldn't be able to make even small changes in functionality without reimplementing that operation in its entirety. If on the other hand the core class breaks up that operation into a template method (see Template Method [GHJV95]) comprising several small and judiciously chosen primitive operations, then it's more likely a programmer can modify or extend just the functionality he wants.
Clock_core class
as generated by the ibuild GUI builder [VT91]:
class Clock_core : public MonoScene {
public:
Clock_core(const char*);
protected:
Interactor* Interior();
virtual void SetTime();
virtual void SetAlarm();
virtual void Snooze();
protected:
Picture* _clock;
SF_Polygon* _hour_hand;
SF_Rect* _min_hand;
Line* _sec_hand;
};
MonoScene is a Decorator class (see Decorator [GHJV95]) for widgets in the InterViews GUI
toolkit [LVC89]. Interactor is
the base class for widgets in InterViews; thus MonoScene
is an Interactor. InterViews also furnishes the
SF_Polygon, SF_Rect, Line, and
Picture classes, which implement graphical objects.
Picture is a Composite class from the Composite pattern,
while the other classes act as Leaf classes in that pattern. Ibuild's
user exported these instances to make them accessible to the core
subclass.
Although Clock_core appears to define just a few member
functions, its interface is really quite large, mainly because
Interactor's interface is. Clock_core
inherits a lot of default behavior too, both from
Interactor and from MonoScene. Of the
operations it adds, only Interior actually does anything:
it assembles the widgets and graphical objects (both exported and
nonexported ones) to form the user interface. Interior
is nonvirtual because the assembly is specified entirely in the
builder, so there's never a need to override it---you'd just redraw it
in the builder.
But we do need to add some behavior programmatically. To add behavior, we modify the core subclass. Here's what it looks like before modification:
class Clock : public Clock_core {
public:
Clock(const char*);
};
The constructor does nothing. Yet thanks to all the generated code
Clock inherits, it is a fully instantiable class that
displays a clock face, albeit with no behavior. We just need to
override some operations.
SetTime, SetAlarm, and Snooze
were specified in the builder. They are the operations that the
corresponding buttons invoke when they're pressed. They do nothing by
default; we override them in Clock to do useful work. We
also need to add code for rotating the hands in response to InterViews
timer events, which the clock needs to receive every second.
The modifications to make this class a full-fledged application are straightforward:
class Clock : public Clock_core {
public:
Clock(const char*);
void Run();
virtual void SetTime();
virtual void SetAlarm();
virtual void Snooze();
virtual void Update();
private:
void GetSystemTime(int& h, int& m, int& s);
void SetSystemTime(int h, int m, int s);
void Alarm();
private:
float _time;
float _alarm;
};
The modified constructor initializes _alarm (which stores
the alarm time) and _time (which stores the time of the
last update) to zero. Run implements the event loop. It
waits up to 1 second for input events and then updates the clock's
appearance to reflect the current time as reported by
GetSystemTime. Run is a template method
that has Alarm and Update as primitive
operations. It calls Alarm when the alarm should go off,
and it calls Update (which was inherited from
Interactor, by the way) to update the clock's appearance.
To minimize redrawing, Update determines the amount of
rotation for each hand from the difference between the current and
last update times. That way it rotates only what has to move.
SetTime, SetAlarm, and Snooze
are overridden to do their jobs. In particular, SetTime
and SetAlarm must pop up dialog boxes (created with
ibuild, of course) to collect data from the user.
GetSystemTime and SetSystemTime are just
helper functions that encapsulate system calls for getting and setting
the system time.
static Singleton* _instance;
and
static Destroyer _destroyer;
in the Singleton class and not just the latter. My
response: An oversight! You only need the latter, assuming the
Destroyer class lets you access the
Singleton instance (it does). Thanks, Richard!
[LVC89] Linton, M., J. Vlissides, P. Calder. "Composing User Interfaces with InterViews." Computer, Vol. 22, No. 2, February 1989, pp. 8--22.
[Vlis96]: Vlissides, J. Pattern Hatching, C++ Report, June 1996.
[VT91] Vlissides. J and S. Tang. "A Unidraw-Based User Interface Builder." Proceedings of the ACM SIGGRAPH Fourth Annual Symposium on User Interface Software and Technology, Hilton Head, SC, November 1991, pp. 201--210.