Pattern Hatching

Generation Gap

John Vlissides

C++ Report, November/December 1996

Forsooth! Eight Pattern Hatching columns without so much as one new pattern. Time to make amends.

PATTERN NAME

Generation Gap

CLASSIFICATION

Class Structural

INTENT

Modify or extend generated code just once no matter how many times it is regenerated.

MOTIVATION

Having a computer generate code for you is usually preferable to writing it yourself, provided the code it generates is

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.



Figure 1

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.



Figure 2



Figure 3

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:

  1. It's messy. Although an improvement over ad hoc modification, the hand-programmed code still mingles with the generated code. The result looks cluttered at best, to the extent that people may turn to tools to make it more readable---by hiding or highlighting different parts on demand, for example. But tools seldom mask the problem completely.
  2. It's pure convention. The compiler can't check for illegal changes.

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.



Figure 4

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.

APPLICABILITY

Apply Generation Gap when all of the following are true:

STRUCTURE

PARTICIPANTS

CoreClass (Clock_core) CoreSubclass (Clock) Client

COLLABORATIONS

CONSEQUENCES

Generation Gap provides the following benefits:

  1. Modifications are decoupled from generated code. All hand-modifications go into the CoreSubclass, encapsulating them and keeping them separate from generated code. As a bonus, the CoreSubclass interface may give insight into the modification by indicating the operations that were overridden or added.
  2. Modifications can have privileged access to implementation details. The inheritance relationship between CoreClass and CoreSubclass means that programmers and tool builders can leverage the full expressiveness of the implementation language to control access to generated internals.
  3. Subsequent regeneration does not require reapplying the modifications. The tool regenerates only the CoreClass, not the CoreSubclass, thereby preserving the modifications across generations. While the modifications will not need to be reapplied, they may need modification themselves if

    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.

There are two main liabilities to the pattern:

  1. Double the number of classes. The pattern introduces a CoreClass/CoreSubclass pair for every class that would have been used. It may also introduce classes where there were none, if for example the generated code was essentially procedural. Each class in a system costs you something, if not storage or execution speed then at least conceptual overhead.
  2. Integrating generated classes into existing class hierarchies may be difficult. Making the core subclass inherit from an existing class requires multiple inheritance. You could achieve the same effect by having the core class inherit from the existing class, but that will involve changing the core class interface, defeating the purpose of the pattern. This problem can be addressed in the code generator by letting the user specify a parent class for the core class.

IMPLEMENTATION

Generation Gap's implementation depends a lot on both the programming environment and the programming language. Consider these two issues:

  1. Disallowing modifications to the core class. Generation Gap's cardinal requirement is that programmers never modify the core class. Unfortunately, ensuring that can be a challenge.

    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.

  2. Controlling access to core class internals. As we saw in the Motivation section, programmers often have to access functionality in the core class to modify or extend it in the core subclass. How easy that is varies with programming language. C++ and Java provide several levels of access control; Smalltalk does not. Hiding information from some classes (and/or objects) and not others may therefore be difficult in your environment.

    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.

SAMPLE CODE

Below is the actual declaration of the 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.

KNOWN USES

Ibuild [VT91] pioneered the use of Generation Gap in a user interface builder. (That's it---just one known use. Now you know why this pattern never made it into Design Patterns. If you know of another application of this pattern, let's hear about it!)

RELATED PATTERNS

Core classes often use template methods (see Template Method [GHJV95]) to maximize reuse of generated functionality.

BUGBAG

(No, this isn't a new pattern section---just a bit of reader feedback to share with you.) Regarding my June 96 column [Vlis96], Richard Gyger over at ObjectSpace was curious about why I defined both
    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!

Footnotes

* An example of such a builder is ibuild, the builder for the InterViews toolkit
[VT91].

References

[GHJV95] Gamma, E., R. Helm, R. Johnson, J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995.

[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.


This page and its contents ©1996 by John Vlissides. All Rights Reserved.