Instructor's Guide
intro
polymorphism
idioms
patterns
events
summary,
Q/A,
literature
Subsections
The hush framework, developed by the author and his colleagues,
aims at providing an easy-to-use
and flexible, multi-paradigm environment
for developing distributed hypermedia and web-based applications.
Actually hush, which stands for
hyper utility shell, is a part of the DejaVU framework
which has been developed at the Free University in Amsterdam over
the last five years.
The DejaVU framework is meant as an umbrella for our
research in object-oriented applications and architectures.
Many of the examples in this book are in some way derived from
hush or applications developed within the DejaVU project.
The hush library was originally developed in C++,
but parts of it have been ported to Java using the Java native
runtime interface. You will see examples of hush
in chapters [4],
[6],
[7],
[11] and
[12].
In this section a brief overview will be given of the basic
concepts underlying hush.
Then we will discuss the idioms used in realizing hush and
its extensions, in particular an adapted version of
the handle/body idiom originally introduced in [Coplien92],
the virtual self-reference idioms and the
dynamic role switching idiom.
At the end of this section we will discuss the implications
these idioms have for developing hush applications.
Readers not interested in hush may safely skip the
introduction that follows and the discussion at the end of this section.
The hush framework is object-oriented in that it allows
for a component-wise approach to developing applications.
Yet, in addition to object class interfaces, it
offers the opportunity to employ a script language, such as Tcl and Prolog,
to develop applications and prototypes.
The hush framework is a multi-paradigm framework,
not only by supporting a
multi-lingual approach, but also by providing support
for distributed client/server solutions in a transparent (read CORBA) manner.
In this section we will look at
the idioms employed
for the realization of the framework.
In developing hush
we observed that there
is a tension between
defining a clean object model and providing the flexibility
needed to support a multiparadigm approach.
We resolved this tension by choosing to differentiate
between the object model (that is class interfaces)
offered to the average user of the framework and
the object model offered to advanced users
and system-level developers.
In this approach, idioms play a central role.
We achieved the desired flexibility by
systematically employing a limited number of basic idioms.
We succeeded in hiding these idioms from the average
user of the framework.
However, the simplicity of our original object model
is only apparent.
Advanced or system-level developers who intend to define
extensions to the framework must be well aware
of the patterns underlying the basic concepts,
that is the functionality requirements of the classes involved,
and the idioms employed in realizing these requirements.
The hush framework -- basic concepts
Application development
generally encompasses a variety of programming tasks,
including system-level software development
(for example for networking or multi-media functionality),
programming the user interface
(including the definition of screen layout
and the responsivity of the interface widgets to user
actions),
and the definition of (high-level) application-specific
functionality.
Each of these kinds of tasks may require
a different approach and possibly
a different application programming language.
For example, the development of the user interface
is often more conveniently done
using a scripting language,
to avoid the waiting times involved
in compiling and linking.
Similarly, defining knowledge-level
application-specific functionality may
benefit from the use of a declarative
or logic programming language.
In developing hush, we decided from the start to
support a multiparadigm approach to software
development and consequently we had to
define the mutual interaction between the
various language paradigms, as for example
the interaction between C++ and a scripting language,
such as Tcl.
Current scripting languages, including Python and Tcl,
provide facilities for being embedded in C and C++,
but extending these languages with functionality defined
in C or C++ and employing the language
from within C/C++ is rather cumbersome.
The hush library offers a uniform
interface to a number of script languages
and,
in addition, it
offers a variety of widgets and multimedia
extensions, which are accessible through
any of the script interpreters as well
as the C++ interface.
These concepts are embodied in (pseudo) abstract
classes that are realized by employing
idioms
extending the handle/body
idiom, as explained later on.
Basic hush classes
- session -- to manage (parts of) the application
- kit -- to provide access to the underlying system and interpreter
- handler -- to bind C++ functionality to events
- event -- stores information concerning user actions or system events
- widget -- to display information on a screen
- item -- represents an element of a widget
slide: Basic hush classes
Programming a hush application requires the
definition of an application class
derived from session to initialize
the application and start the (window environment)
main loop.
In addition, one may bind Java or C+= handler objects
to script commands by invoking the kit::bind
function.
Handler objects are to be considered
an object realization of callback functions,
with the advantage that client data may be
accessed in a type-secure way
(that is either by resources stored when creating the
handler object or by information that is passed via events).
When invoked, a handler object receives a pointer to
an event
(that is, either an X event or an event related to
the evaluation of a script command).
Both the widget and (graphical) item class are
derived from handler to
allow for declaring widgets and items to be their own handler.
Embedding script interpreters
The hush framework offers a generic kit
that may be used as the interface to any embedded
interpreter.
The public interface of the kit class looks
as follows:
interface kit { kit
void eval(string cmd);
string result();
void bind(string name, handler h);
};
The function eval is used for evaluating
(script) commands, and result may be used to
communicate data back.
The limitation of this approach, obviously,
is that it is purely string based.
In practice, however, this proves to be flexible
and sufficiently powerful.
The bind function may be used to
define new commands and associate it with
functionality defined in handler objects,
which are introduced below.
Handler objects
The problem of extending the script language
with functionality defined by the application,
is (as already indicated above) addressed by defining
a generic handler object class.
Handler objects may be regarded as
a generalization of callback functions,
in the sense that they are activated whenever the
corresponding script command is evaluated.
The advantage of using objects for
callbacks instead of functions, obviously,
is that we no longer need type-insecure casts,
or static or global variables
to pass information around.
The public interface of the handler class looks as follows:
interface handler { handler
int dispatch( event e ); to dispatch events
int operator();
};
The dispatch function is called by the underlying system.
The dispatch function receives a pointer to
an event which encodes the information
relevant for that particular callback.
In its turn dispatch calls the operator() function.
Classes derived from handler need only redefine
the operator() function.
Information needed when activating a handler
object must be provided when creating the object,
or obtained from the event for which the handler is activated.
The use of handler objects is closely connected
to the paradigm of event-driven computation.
An event, conceptually speaking,
is an entity that is characterized by two significant
moments,
the moment of its creation and the
moment of its activation, its occurrence.
Naturally, an event may be activated multiple times
and even record a history of its activation,
but the basic principle underlying the use of events
is that all the information that is needed is
stored at creation time and, subsequently,
activation may proceed blindly. See section [reactor].
User actions
Another use of handler objects (in hush)
is for defining what must be done
in response to user events, resulting from
actions such as moving the mouse, or pressing
a button, or selecting an entry from a menu.
This is illustrated by the public interface of
the generic widget class:
interface widget : handler { widget
...
void bind( handler h );
void bind( string action, handler h );
...
};
The first member function bind may be
used for installing a handler for the default bindings
of the widget,
whereas the second bind function is to be used
for overriding any specific bindings.
(Recall that the class widget is derived from
handler class to allow the widget to
be its own handler.
In this way inheritance or the delegation to
a separate handler object may be used
to define the functionality of a widget.)
In addition to the widget class, the hush library
also provides the class item, representing graphical
items.
Graphical items, however, are to be placed within
a canvas widget, and
may be tagged to allow for the groupwise manipulation
of a collection of items, as for example moving
them in response to dragging the mouse pointer.
Programmer-defined events
User interface events occur in response
to actions by the user.
They are scheduled by the underlying window system,
which invokes the handler whenever it is convenient
or necessary.
When getting used to event-driven computation,
system designers and programmers may feel
the need to have events at their disposal that may
be scheduled at will, under the programmer's control.
It will come as no surprise that another use
of handler objects is to allow for programmer-defined
events.
The public interface of the class event
looks as follows:
interface event : handler { event
operator();
};
Actual event classes are derived from the generic class
event,
and a scheduler is provided to activate events
at the appropriate time.
(In effect, we provide a fully functional discrete
event simulation library,
including facilities for generating
random distributions and analysing the outcome of experiments.
Business process simulations done with this library
are discussed in Chapter 11.)
Note that there is an important difference between
programmer-defined events and system-defined events.
System-defined events are delivered to the user
by activating a handler callback.
In contrast, programmer-defined events are (directly)
activated by a scheduler. They contain, so to speak,
their own handler.
Discussion
What benefits do we derive from employing
handler objects and their derivatives?
One advantage is that we have a uniform way
to define the functionality of script commands,
callbacks to user actions
and programmer-controlled events.
Another, less apparent advantage,
is that it allows us to incorporate
a variety of functionality
(including sound synthesis facilities,
digital video and active documents)
in a relatively straightforward fashion.
The handle/body idiom
Instructor's Guide
intro
polymorphism
idioms
patterns
events
summary,
Q/A,
literature
The handle/body class idiom,
originally introduced in [Coplien92],
separates the class defining a component's
abstract interface (the handle class) from its
hidden implementation (the body class). All intelligence is
located in the body, and (most) requests to the handle object
are delegated to its implementation.
In order to illustrate the idiom,
we use the following class as a running example:
class A { A -- naive
public A() { }
public void f1() { System.out.println("A.f1"); f2(); }
public void f2() { System.out.println("A.f2"); }
};
slide: Running example
The implementation of A is straightforward and does not
make use of the handle/body idiom.
A call to the f1() member function of A will print a message and
make a subsequent call to f2().
Without any modification in the behavior of A's instances, it is possible
to re-implement A using the handle/body idiom. The member functions of
class A are implemented by its body, and A is reduced to a simple
interface class:
class A { A
public A() { body = new BodyOfA(this); }
protected A(int x) { }
public void f1() { body.f1(); }
public void f2() { body.f2(); }
public void f3() { System.out.println("A.f3"); }
private A body;
};
slide: Interface: A
Note that the implementation of A's body
can be completely hidden from the application programmer.
In fact, by declaring A to be the superclass of its body class,
even the existence of a body class can be hidden. If A is a class
provided by a shared library, new implementations of its body class
can be plugged in, without the need to recompile dependent applications:
class BodyOfA extends A { BodyOfA -- naive
public BodyOfA() { super(911); }
public void f1() { System.out.println("A.f1"); f2(); }
public void f2() { System.out.println("A.f2"); }
};
slide: Naive: BodyOfA
In this example, the application of the idiom has only two
minor drawbacks. First, in the implementation below,
the main constructor of A
makes an explicit call to the constructor of its body class.
As a result, A's constructor
needs to be changed whenever an alternative implementation of the
body is required. The Abstract Factory pattern described in
[GOF94]
may be used to solve this problem in a generic
and elegant way.
Another (aesthetic) problem is the need for the dummy constructor
to prevent a recursive chain of constructor calls.
But the major drawback of the handle/body idiom occurs when deriving a
subclass of A which partially redefines A's virtual member functions.
Consider this definition of a derived class C:
class C extends A { C
public void f2() { System.out.println("C.f2"); }
};
slide: Usage: C
Try to predict the output of a code fragment like:
C c = new C; c.f1();
slide: Example: calling C
The behavior of instances of C does indeed depend on whether
the hidden implementation of its base class A applies the
handle/body idiom or not! If it does,
the output will be A.f1() A.f2().
because the indirect call to f2() in
f1() will (unexpectedly) not call the redefined version of
f2().
The original definition of A would of course
yield A.f1() C.f2().
but this can only be obtained by deriving C
directly from the (hidden) body class.
Note that this is an illustration
of one of the main drawbacks of the OOP paradigm: the inability
to change base classes at the top of a
hierarchy without introducing errors in derived classes.
Explicit invocation context
In both implementations of A,
the call to f2() in f1() is an abbreviation of
this.f2().
However, in the first, naive implementation of A,
the implicit this reference
refers to the handle object (which can be an instance of a derived class).
In contrast, this in the BodyOfA
will refer to the body object.
As a consequence, the body object is
unable to make calls to functions redefined by classes derived from
the base class A.
We use the term invocation context to denote a reference to the context
in which the original request for a specific service is made, and
represent this by a pointer to the handle object.
In other words,
the handle object needs a pointer to its body to be able to
delegate its functionality, and, symmetrically,
the body needs a pointer to the handle
in order to be able to use any redefined virtual functions.
The body can be redefined as:
class BodyOfA extends A { BodyOfA
public BodyOfA(A h) { super(911); handle = h; }
public void f1() { System.out.println("A.f1"); handle.f2(); }
public void f2() { System.out.println("A.f2"); }
A handle; reference to invocation context
};
slide: Handle/Body: BodyOfA
The new body class is aware of the fact that it
is implementing services which are accessed via the handle object.
Consequently, it can use this information and is able to make calls
to functions which might be redefined by descendants of A.
Note that this solution does require some programming discipline:
all (implicit) references to the body object should be changed into a
reference to the invocation context.
Fortunately, this discipline is only required in the body classes of the
implementation hierarchy.
slide: Separating interface hierarchy and implementation
Descendants of the handle classes in the public interface
hierarchy can share and redefine code implemented by the hidden
body classes in a completely transparent way, because all code sharing
takes place indirectly, via the interface provided by the handle classes.
However, even other body classes will typically share code via the
handle classes.
Also derived classes can use the handle/body idiom,
as depicted in slide [handle-body].
Instructor's Guide
intro
polymorphism
idioms
patterns
events
summary,
Q/A,
literature
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, look at the item class below:
class item { item
public item(String x) { _name = x; _self = null; }
String name() { return exists()?self().name():_name; }
public void redirect(item x) { _self = x; }
boolean exists() { return _self != null; }
public item self() { return exists()?_self.self():this; }
item _self;
String _name;
};
slide: Item with self()
The item class has an instance variable _self,
that can be set to an arbitrary instance of item
by invoking redirect.
Now, when we ask for the name of the item,
it is checked whether a redirection exists.
If so, the call is redirected to the instance referenced
by self(), otherwise the name of the item
itself is returned.
public class go {
public static void main(String[] args) {
item a = new item("a");
item b = new item("b");
a.redirect(b);
System.out.println(a.name()); indeed, b
}
};
slide: item: go
In combination with the handle/body idiom,
we can create composites offering the interface
of item, providing access to one or
more (inner) items.
This will be further illustrated in chapter 4.
Those well-versed in design patterns will recognize the Decorator
patterns (as applied in the Interviews MonoGlyph class,
[Interviews]).
Dynamic role-switching
Instructor's Guide
intro
polymorphism
idioms
patterns
events
summary,
Q/A,
literature
For many applications, static type hierarchies
do not provide the flexibility needed
to model dynamically changing roles.
For example we may wish to consider a person
as an actor capable of various roles during his lifetime,
some of which may even coexist concurrently.
The characteristic feature of the
dynamic role switching idiom underlying the
actor pattern is that it
allows us to regard a particular entity from multiple
perspectives
and to see that the behavior of that entity changes accordingly.
We will look at a possible realization of the idiom below.
Taking our view of a person as an actor
as a starting point,
we need first to establish the repertoire of possible behavior.
class actor {
public static final int Person = 0;
public static final int Student = 1;
public static final int Employer = 2;
public static final int Final = 3;
public void walk() { if (exists()) self().walk(); }
public void talk() { if (exists()) self().talk(); }
public void think() { if (exists()) self().think(); }
public void act() { if (exists()) self().act(); }
public boolean exists() { return false; }
public actor self() { return this; }
public void become(actor A) { }
public void become(int R) { }
};
slide: actor.java
Apart from the repertoire of possible behavior,
which consists of the ability to walk, talk,
think and act,
an actor has the ability to establish its own
identity (self) and to check whether it exists
as an actor, which is true only if it has become another self.
However, an actor is not able to assume a different role
or to become another self. We need a person for that!
Next, we may wish to refine the behavior of an actor
for certain roles, such as for example the student
and employer roles, which are among the many roles
a person can play.
class student extends actor { student
public void talk() { System.out.println("OOP"); }
public void think() { System.out.println("Z"); }
};
class employer extends actor { employer
public void talk() { System.out.println("money"); }
public void act() { System.out.println("business"); }
};
slide: Students and Employers
Only a person has the ability
to assume a different role or to assume a different identity.
Apart from becoming a Student or Employer,
a person may for example become an adult_person
and in that capacity again assume a variety of roles.
class person extends actor { person
public person() {
role = new actor[ Final+1 ];
for( int i = Person; i <= Final; i++ ) role[i]=this;
become(Person);
}
public boolean exists() { return role [role] != this; }
public actor self() {
if ( role[ Person ] != this ) return role[ Person ].self();
else return role [role];
}
public void become(actor p) { role[ Person ] = p; }
public void become(int R) {
if (role[ Person ] != this) self().become(R);
else {
_role = R;
if ( role [role] == this ) {
switch(_role) {
case Person: break; nothing changes
case Student: role [role] = new student(); break;
case Employer: role [role] = new employer(); break;
case Final: role [role] = new actor(); break;
default: break; nothing happens
}
}
}
}
int _role;
actor role[];
};
slide: person.java
A person may check whether he exists as a Person,
that is whether the Person role differs from the person's
own identity.
A person's self may be characterized as the actor
belonging to the role the person is playing,
taking a possible change of identity into account.
When a person is created, his repertoire is still empty.
Only when a person changes identity by becoming
a different actor (or person) or by assuming
one of his (fixed) roles, is he capable of displaying
actual behavior.
Assuming or `becoming' a role results in creating
a role instance if none exists and setting the _role
instance variable to that particular role.
When a person's identity has been changed,
assuming a role affects the actor that
replaced the person's original identity.
(However, only a person can change roles!)
The ability to become an actor allows us to model
the various phases of a person's lifetime
by different classes, as illustrated by
the adult class.
class adult extends person { adult
public void talk() { System.out.println("interesting"); }
};
slide: adult.java
In the example code below we have a person
talking while assuming different roles.
Note that the person's identity may be restored
by letting the person become its original self.
public class go { example
public static void main(String[] args) {
person p = new person(); p.talk(); empty
p.become(actor.Student); p.talk(); OOP
p.become(actor.Employer); p.talk(); money
p.become(new adult()); p.talk(); interesting
p.become(actor.Student); p.talk(); OOP
p.become(p); p.talk(); old role: employer
p.become(actor.Person); p.talk(); // initial state
}
};
slide: go.java
The dynamic role switching idiom
can be used in any situation where we wish
to change the functionality of
an object dynamically.
It may for example be used to incorporate
a variety of tools in a drawing editor,
as illustrated in chapter 4.
The art of hush programming
Instructor's Guide
intro
polymorphism
idioms
patterns
events
summary,
Q/A,
literature
For the average user, programming in hush
amounts (in general) to instantiating widgets and
appropriate handler classes,
or derived widget classes that define their own
handler.
However, advanced users and system-level programmers
developing extensions are required to comply
with the constraints resulting
from the patterns underlying the design of hush
and the application of their associated idioms
in the realization of the library.
The design of hush and its extensions can be understood
by a consideration of two basic patterns
and their associated idioms,
that is the nested-component pattern
(which allows for nesting components that have a similar interface)
and the actor pattern
(which allows for attributing different modes or roles
to objects).
The realizations of these patterns are based on idioms
that extend an improved version of the familiar
handle/body idiom.
Our improvement concerns the introduction of
an explicit invocation context which is needed to
repair the disruption of the virtual function call mechanism
caused by the delegation to `body implementation' objects.
In this section, we will first discuss the handle/body idiom
and its improvement.
Then we will discuss the two basic patterns underlying
the design of hush
and we will briefly sketch their realization by extensions of
the (improved) handle/body idiom.
Invocation context
The handle/body idiom is one of the most popular idioms.
It underlies
several other idioms and patterns
(e.g. the envelope/letter idiom, [Coplien92]; the Bridge and Proxy
patterns, [GOF94]).
Invocation context
handle/body
- Problem
- Inheritance breaks with handle/body
- Background
- Envelope/Letter, hiding implementations
- Realization
- Explicit invocation contact in body
- Usage
- sessions, events, kits, widgets, items
slide: Invocation context
However, despite the fact that it is well documented
there seems to be a major flaw in its realization.
Its deficiency lies in the fact that the
dynamic binding mechanism is disrupted by introducing
an additional level of indirection
(by delegating to the `body' object),
since it is not possible to make calls to member functions which
are refined by subclasses of the (visible) handle class in the
implementation of the (hidden) body class. We restored the working of
the normal virtual function mechanism by introducing the notion of
explicit invocation context. In this way, the handle/body
idiom can be applied completely transparently, even for
programmers of subclasses of the handle.
The (improved version of) the idiom is frequently used
in the hush class library.
The widget library is build of a stable
interface hierarchy, offering several common GUI widgets classes like
buttons, menus and scrollbars. The widget (handle) classes are
implemented by a separate, hidden implementation hierarchy, which
allows for changing the implementation of the widget library, without the
need to recompile dependent applications. Additionally, the idiom
helps us to ensure that the various widget implementations are used in
a consistent manner.
The nested component pattern
The nested component pattern has been introduced
to support the development of compound widgets.
It allows for (re)using the script and C++ interface
of possibly compound widgets, by employing explicit redirection to
an inner or primary component.
Nested components
virtual self-reference
- Problem
- Realizing composites with single inheritance
- Background
- Decorators, prototypes
- Realization
- Smart delegation
- Usage
- Composite widgets, embedded logic
slide: Nested components
Inheritance is not always a suitable technique for code sharing
and object composition. A familiar example is the combination of a
Text object and two scrollbars into a ScrollableText object.
In that case, most of the functionality of ScrollableText will
be equal to that of the Text object.
This problem may be dealt with by employing multiple
inheritance.
Using single inheritance, it may be hard to
inherit this functionality directly and add extra functionality
by attaching the scrollbars,
especially when interface inheritance and implementation inheritance
coincide.
The nested component pattern is closely
related to the Decorator pattern treated in [GOF94] and InterViews' notion of
MonoGlyph, [Interviews]. Additionally, by using explicit delegation
it provides an alternative form of code sharing to inheritance,
as can be found in languages supporting prototypes or
exemplars,
see section [prototypes].
The nested component pattern is realized by
applying the virtual self-reference idiom.
Key to the implementation of that idiom is the virtual
self() member of a component. The self()
member returns a
reference to the object itself (e.g. this in C++) by default,
but returns the inner component if the outer object explicitly
delegated its functionality by using the redirect() method.
Note that chasing for self() is recursive,
that is (widget) components can be nested to arbitrary depth.
The self() member must be used to access the functionality
that may be realized by the inner component.
The nested component pattern is employed
in designing the hush widget hierarchy.
Every (compound) widget
can delegate part of its functionality to an inner component.
It is common practice to derive a compound widget from another
widget by using interface inheritance only, and to delegate
functionality to an inner component by explicit redirection.
The actor pattern
The actor pattern provides
a means to offer a multitude of functional modes
simultaneously.
For example, a single kit object gives access to
multiple (embedded) script interpreters,
as well as (possibly) a remote kit.
Actor pattern
dynamic role switching
- Problem
- Static type hierarchies may be too limited
- Background
- State transitions, self-reference
- Realization
- Dynamic instantiation and delegation
- Usage
- Web viewer, kit -- embedded logic
slide: Actor pattern
The characteristic feature of the actor
pattern is that it allows us to regard a particular entity
as being attributed various roles or modes
and that the behavior of that entity changes
accordingly.
Changing roles or modes can be regarded
as some kind of state transition, and
indeed the actor pattern
(and its associated dynamic role-switching idiom)
is closely related to the
State pattern treated in [GOF94]. In both cases, a single object is used to
access the current role (or state) of a set of several role (or
state) classes.
In combination with the virtual self-reference
idiom, our realization of
the actor pattern allows for changing
the role by installing a new actor.
The realization of the actor pattern employs the
dynamic role-switching idiom,
which is implemented by extending the handle class
with a set of several bodies instead of only one.
To enable role-switching,
some kind of indexing is needed. Usually, a dictionary
or a simple array of roles will be sufficient.
In the hush library the actor pattern is used
to give access to multiple
interpreters via the same interface class (i.e. the kit class).
The pattern is essential in supporting the multi-paradigm nature
of the DejaVU framework.
In our description of the design of the Web components
in section [Web],
we will show how dynamic role-switching is employed
for using
various network protocols via the same (net)client class.
The actor pattern is also used to define a (single)
viewer class that is
capable of displaying documents of various MIME-types
(including SGML, HTML, VRML).