[2]
DejaVU Online:
Principles of Object-Oriented Software Development
(©)
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
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
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.
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.
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 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.
interface widget : handler { widget
...
void bind( handler h );
void bind( string action, handler h );
...
};
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.
interface event : handler { event
operator();
};
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"); }
};
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;
};
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"); }
};
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"); }
};
C c = new C; c.f1();
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
};
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.
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;
};
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
}
};
Those well-versed in design patterns will recognize the Decorator
patterns (as applied in the Interviews MonoGlyph class,
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 { 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) { }
};
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"); }
};
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[];
};
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"); }
};
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
}
};
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.
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 is closely
related to the Decorator pattern treated in
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
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
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).
|
Hush Online Technology
hush@cs.vu.nl
12/29/99 |
|
|