Implementation and design -- patterns


introduction, concepts, components, examples, patterns, experience, conclusions, references
In the design and realization of our framework, we have employed a variety of strategies and techniques that now have commonly become known as design patterns and idioms. With reference to the catalogue presented in  [GOF94], we may remark that we employed both the Factory and Singleton pattern in a number of cases, for example in realizing the kit class (to be able to select an appropriate interpreter in a flexible way, and to enforce that there will be only one instance of the interpreter selected). Although we do not wish to claim that we have developed any new patterns, our approach with respect to the realization of patterns differs from approach usually followed in its emphasis on abstract classes, its use of explicit delegation and the employment of what we may call 'virtual' self-reference.

Choosing an interpreter

The envelope/letter idiom (originally introduced in  [Coplien92] is increasingly adopted as a standard approach to separate the definition of a high level interface and its realization by an implementation class. Usually, this involves two classes, say an abstract class A and its implementation class AImpl. A disadvantage of this approach is that an additional (implementation class) is added to the class (name) space. Another disadvantage is that refining the behavior of a class using inheritance becomes more cumbersome.

To illustrate our approach, look at the definition of the class kit:

  class kit {
  public:
    kit( char* kind = "tcl" );

    virtual int eval(char* cmd) { return _kit?_kit->eval(cmd):0; }
    ...
  protected:
    kit( kit* );
    kit* _kit;
  };
The constructor of kit indicates as a default a Tcl interpreter which is encapsulated by the class:
  class tcl_kit : public kit {
  public:
    tcl_kit() : kit(0) { ... }
    ...
  };
The constructor of kit assigns the tcl_kit instance to its _kit variable. In contrast, the tcl_kit initializes its (parent) _kit variable to zero. For the kit instance created by the user, calls are delegated to the tcl_kit instance. However, the user may still inherit from kit and selectively override its functions.

Multiple perspectives on events

One of the problems encountered in developing our framework is that the same class name may be claimed by multiple components. A notable example is the (class) name event, that applies both to the events generated by the (X) window system and to user-defined events. Instead of definign two event classes (such as xevent and simevent) to distinguish between the different notions of event, we decided to apply a similar technique as for the kit class and offer a single class event that may be viewed from multiple perspectives. The class definition of event looks as follows:
  class event : public handler {
  public:

    event( event* x ); // expects x event
    event( );          // creates sim event

    // X event interface

    virtual int type() { return _event?_event->type():0; }

    virtual int x() { return _event?_event->x():0; }
    virtual int y() { return _event?_event->y():0; }

    // simulation interface

    virtual void timestamp();
    virtual double timespent() { return _event?_event->timespent():0; }

  protected:
    event* -event;
  };
The disadvantage of this approach, clearly, is that we need to offer a fat interface (which could in principle be factored using multiple inheritance, at the expense of additional classes).

The (public) constructor event::event(event*) is used to encapsulate events generated by the (X) window system. An advantage of this approach is instance may be accessed via both pointer and object references, without sacrificing dynamic binding. (To our taste, it is more natural to write e.x() than e->x() when accessing the x coordinate of the mouse pointer stored in an event instance.)

For events that are to be scheduled under programmer's control, a class must be specified inheriting from event (overriding the operator() activation function). In that case a simulation event is created containing the additional data structures needed for bookkeeping and scheduling.

Virtual self-reference


introduction, concepts, components, examples, patterns, experience, conclusions, references
A special feature of the hush widget class library is its support for the definition of new composite widgets, which provide to the application programmer the interface of a built-in (or possibly other composite) widget. To realize this flexibility, we introduced a self() function that adds another level of indirection to self-reference. For example, we may define a drawtool widget, which provides for both the C++ programmer and the (Tcl) script programmer the interface of an ordinary (built-in) canvas:
  class drawtool : public canvas {
  public:
    drawtool() : canvas() { }     // to declare drawtool
    drawtool(char* path) : canvas() {
	initialize( path );             // constructs the components
	redirect( _canvas );      // delegate to _canvas component
	}
    int operator()() {              // interface to Tcl
	if ( !strcmp("drawtool",argv[1]) return create(argc, argv);
	else return self()->eval( flatten(argc, argv) );
	}
  private:
    void initialize( char* path ); 
    int create( int argc, char* argv[] );
    canvas* _canvas;
  };
For completeness, the first constructor is only used to define the script command drawtool as in
  tk->action("drawtool", new drawtool() );
which may occur in main or prelude of the application class.

The call redirect(_canvas) in the regular constructor results in delegating both member function calls and script commands addressed at the drawtool widget to the canvas widget pointed to by _canvas, which is assumed to be created in initialize. The _canvas widget acts, so to speak, as the primary component of drawtool in that it receives all bindings and (configuration) commands. Actual redirection occurs by calling self(). The definition of widget::self() is simply:

  widget* self() { return _self?_self->self():this; }
In other words, it checks the private _self variable of the widget, which may be given a value by widget::_redirect, until _self is undefined (zero), in which case the this pointer is returned. In this way primary widget components can be embedded at arbitrary depth within a composite widget, without any need to adapt the interface of the embedding (composite) widget.

Those well-versed in design patterns will recognize the Decorator patterns (as applied in the Interviews MonoGlyph class  [Interviews89]).


introduction, concepts, components, examples, patterns, experience, conclusions, references