After studying general issues in the design and software engineering of object-oriented applications and frameworks, it is time to focus in somewhat more detail on actual application development.
In this chapter we will look at the drawtool application, as a representative of a broader category of interactive editing tools.
Additional keywords and phrases:
hush framework, interactive editors, law of Demeter,
formal specification in Z, abstract systems
After that we will treat some miscellaneous issues in the design of classes. This chapter will be concluded with a case study, a concise, yet detailed, example of a more formal approach to the development of an object-oriented application.
In the Software Engineering curriculum at the Vrije Universiteit, we have repeatedly used interactive editors as a medium-term assignment for CS2 students (five weeks for groups of four or five students). One example of such an assignment is the Interactive Design Assistant discussed in section IDA. Another example is the musical score editor (see appendix Projects), which has been chosen by a selected group of CS3 and CS4 students as a practical assignment for the Object-Oriented Software Development course.
In this section we will look at the drawtool application, which is a representative realization of a (rather simple) drawing editor. The implementation of drawtool presented here is realized in the Java version of the hush framework. The hush C++ framework has been used for a number of years in the Software Engineering curriculum, but has recently been replaced by Java with Swing. The drawtool application is nevertheless interesting because it acted for many years as the basic example of an interactive editor for quite a number of students.
Before studying drawtool, we will first look at
the realization of a drawing canvas in hush
A simple drawing canvas in hush
In the widget class hierarchy depicted on the right in slide hush-overview, the widget class represents an abstract widget, defining the commands that are valid for each of the descendant concrete widget classes. The widget class, however, is not an abstract class in Java or C++ terms. It may be used for creating references to widgets defined in Tcl. In contrast, employing the constructor of one of the concrete widget classes results in actually creating a widget.
As an example look at the code for the drawing canvas
widget depicted in slide drawing-canvas.
For the draw class we must distinguish between
a handler and a canvas part.
The handler part is defined by the methods
press, release and motion.
The canvas part allows for drawing figures, such as a small circle.
Widgets may respond to events.
To associate an event with an action,
an explicit binding must be specified
for that particular widget.
Some widgets provide default bindings.
These may, however, be overruled.
The function bind is used to associate
handlers with events.
The first string parameter of
bind may be used to specify
the event type.
Common event types are,
for example,
ButtonPress, ButtonRelease
and Motion, which are the
default events for canvas widgets.
Also keystrokes may be defined as events,
for example Return,
which is the default event for
the entry widget.
The function may be
used to associate a handler object
with the default bindings
for the widget.
Concrete widgets may not override the bind
function itself, but must define the protected
function install.
Typically, the install function consists
of calls to bind
for each of the event types that is
relevant to the widget.
In addition, the widget class offers two
functions that may be used
when defining compound or mega widgets.
The function must by used
to delegate the invocation
of the eval, configure and bind functions
to the widget w.
The function gives access to the widget
to which the commands are redirected.
The function path will still deliver
the path name of the outer widget.
Calling redirect when creating the compound widget
class suffices for most situations.
However, when the default events must be changed or the
declaration
of a handler must take effect for several component widgets,
the function install must be redefined
to handle the delegation explicitly.
A structural view of the drawtool application is given
in slide drawtool-structure.
Usually, the various widgets constituting the user
interface are (hierarchically) related to each other,
such as in the drawtool application which contains
a canvas to display graphic elements,
a button toolbox for selecting the graphic
items and a menubar offering various options
such as saving the drawing in a file.
Widgets in Tk are identified by a path name.
The path name of a widget reflects its possible
subordination to another widget.
See slide widget-hierarchy.
A menubar consists of menubuttons to which actual menus are attached.
Each menu consists of a number of entries,
which may possibly lead to cascaded menus.
The second button of the menubar is defined by the
EditMenu.
The EditMenu requires a toolbox and creates
a menubutton.
It configures the button and
defines a menu containing two entries,
one of which is a cascaded menu.
Both the main menu and the cascaded menu
are given the toolbox as a handler.
This makes sense only because
for our simple application
the functionality offered by
the toolbox and EditMenu coincide.
In slide object-invariant, we have defined a class employee.
The main features of an employee are the (protected)
attribute sal (storing the salary of an employee)
and the methods to access and modify the salary
attribute.
For employee objects,
the invariant (expressing that any amount k
is equal to the salary of an employee whose
salary has been set to k)
clearly holds.
Now imagine that we distinguish between ordinary employees
and managers by adding a permanent
bonus when paying the salary of a manager,
as shown in slide hidden-bonus.
The reader may judge whether this example is realistic or not.
-- A method m is a client of C if m calls a method of C
-- If m is a client of C then C is a supplier of m
-- C is an acquaintance of m if C is a supplier of m but not (the type of) an argument of m or (of) an instance variable
of the object of m
The guideline concerning the use of safe suppliers is known
as the Law of Demeter, of which the underlying
intuition is that the programmer should not be bothered
by knowledge that is not immediately apparent
from the program text (that is the class interface)
or founded in well-established conventions
(as in the case of using special global variables).
See slide 4-demeter.
Do not refer to a class C in a method m unless C is (the type of)
In addition, one must establish
the relationships between the object classes
constituting the system and
delineate the facilities the system offers to the user.
Such facilities are usually derived from
a requirements document and may be formally specified
in terms of abstract operations on the system.
In this section we will look at
the means we have available to express
the properties of our object model,
and we will study how we may
employ abstract specifications of system
operations to arrive at the integration
of user actions and the object model
underlying a system in a seamless way.
The approach sketched may be characterized
as event-centered.
In contrast, objects (in the more general sense)
usually hide object and non-object data members,
and instead provide a method interface.
Moreover, object-oriented modeling focuses on
behavioral properties, whereas semantic modeling has been
more concerned with (non-behavioral) data types
and (in the presence of inheritance) data subtypes.
Relations, as may be expressed in the entity-relationship
model, can partly be expressed directly in terms
of the mechanisms supported by object-oriented
languages.
For instance, the is-a relation corresponds
closely (although not completely) with
the inheritance relation.
See slide 3-challenges.
Both the has-a and uses relation is
usually implemented by including (a pointer to)
an object as a data member.
Another important relation is the is-like relation,
which may exist between objects that are neither related
by the inheritance relation nor by the subtype relation,
but yet have a similar interface and hence may be regarded
as being of analogous types.
The is-like relation may be enforced by parametrized
types that require the presence of particular methods,
such as a compare operator in the case of a generic
list supporting a sort method.
In the following, an outline of
the specification language Z will be given.
More importantly, the specification
of a simple library system will be discussed,
illustrating how we may specify
user actions in an abstract way.
(The use of the Z specification language
is in this respect only of subsidiary
importance.)
In the subsequent section, we will look
at the realization of the library
employing an abstract system of objects
and events corresponding to the user
actions, which reflects the characterization
given in the formal specification.
The specification language Z is based on classical
(two-valued) logic and set theory.
It has been used in a number of industrial projects,
Similarly, we may specify the operations
for the $Bounded::Counter$ by including
the corresponding operations
specified for the Counter, adding conditions
if required.
From a schema we may easily extract the
pre-conditions for an operation by removing from
the conditions the parts involving
a primed variable.
Clearly, the post-condition is then
characterized by the conditions thus eliminated.
For example, the pre-condition of the
$Counter::Incr$
operation is $v? \geq 0$, whereas
the post-condition is $n' = n + v?$
which corresponds to the implementation requirement
that the new value of the Counter
is the old value plus the value of the
argument $v?$.
In a similar way, the pre-condition
for applying the $Bounded::Incr$ operation
is $n + v? \leq Max$.
Note, however, that this pre-condition
is stronger than the original
pre-condition $v? \geq 0$, hence to conform
with the rules for refinement
we must specify what happens when
$ n + v? > Max $ as well.
This is left as an exercise for the reader.
Clearly, although Z lacks a notion
of objects or classes, it may conveniently
be employed to specify the behavior
of an object.
In
In contrast to Eiffel, which offers only
a semi-formal way in which to specify the behavior
of object classes, Z allows for
a precise formal specification of
the requirements a system must meet.
To have the specification reflect
the object structure of the system
more closely, one of the extensions
of Z mentioned above may be used.
An example of using (plain) Z
to specify the functionality
of a library system is given below.
For
Note that lending a book involves
both the invocation of $book::borrow$
and $person::allocate$.
This could easily be simplified by extending the function
$book::borrow$ and $book::_return$
with the statements $p->allocate(this)$
and $p->deallocate(this)$ respectively.
However, I would rather take the opportunity to illustrate the use
of events, providing a generic solution
to the interaction problem noted.
The Borrow event class provides a controlled
way in which to effect the borrowing of a book.
In a similar way, a Return event class may be defined.
Events are primarily used
as intermediate between the user (interface)
and the objects comprising the library system.
For the application at hand, using events
may seem to be somewhat of an overkill.
However,
events not only give a precise characterization of
the interactions involved but, equally importantly,
allow for extending the repertoire
of interactions without disrupting
the structure of the application
simply by introducing additional event types.
The original paper on hush is draft version 0.1 (15/7/2001)
boolean dragging;
public draw(String path) {
super(path);
dragging = false;
bind(this);
}
public void press(event ev) {
dragging = true;
}
public void release(event ev) {
dragging = false;
}
public void motion(event ev) {
if (dragging)
circle(ev.x(),ev.y(),2,"-fill black");
}
};
The drawtool application
The toolbox component
tablet tablet;
public toolbox(widget w, tablet t) {
super(w,"toolbox");
tablet = t;
new toolbutton(this,"draw");
new toolbutton(this,"move");
new toolbutton(this,"box");
new toolbutton(this,"circle");
new toolbutton(this,"arrow");
}
public int operator() {
tablet.mode(_event.arg(1)); // reset tablet mode
return OK;
}
};
public toolbutton(widget w, String name) {
super(w,name);
text(name);
bind(w,name);
pack("-side top -fill both -expand 1");
}
};
The menubar component
public menubar(widget w, tablet t, toolbox b) {
super(w,"bar");
configure("-relief sunken");
new FileMenu(this,t);
new EditMenu(this,b);
new HelpButton(this);
}
};
The tablet component
int _mode;
canvas canvas;
handler[] handlers;
final int DRAW = 0;
final int MOVE = 1;
final int CIRCLE = 2;
final int BOX = 3;
final int ARROW = 5;
public tablet(widget w, String name, String options) {
super(w,name,"*");
handlers = new handler[12];
init(options);
redirect(canvas); // to delegate to canvas
bind(this); // to intercept user actions
handlers[DRAW] = new DrawHandler(canvas);
handlers[MOVE] = new MoveHandler(canvas);
handlers[BOX] = new BoxHandler(canvas);
handlers[CIRCLE] = new CircleHandler(canvas);
handlers[ARROW] = new ArrowHandler(canvas);
_mode = 0; // drawmode.draw;
}
public int operator() {
handlers[_mode].dispatch(_event);
return OK;
}
public int mode(String s) {
int m = -1;
if ("draw".equals(s)) m = DRAW;
if ("move".equals(s)) m = MOVE;
if ("box".equals(s)) m = BOX;
if ("circle".equals(s)) m = CIRCLE;
if ("arrow".equals(s)) m = ARROW;
if (m >= 0) _mode = m;
return _mode;
}
void init(String options) {
widget root = new frame(path(),"-class tablet");
canvas = new canvas(root,"canvas",options);
canvas.configure("-relief sunken -background white");
canvas.geometry(200,100);
scrollbar scrollx = new Scrollbar(root,"scrollx");
scrollx.orient("horizontal");
scrollx.pack("-side bottom -fill x -expand 0");
scrollbar scrolly = new Scrollbar(root,"scrolly");
scrolly.orient("vertical");
scrolly.pack("-side right -fill y -expand 0");
canvas.pack("-side top -fill both -expand 1");
canvas.xscroll(scrollx); scrollx.xview(canvas);
canvas.yscroll(scrolly); scrolly.yview(canvas);
}
};
The drawtool class
widget root;
tablet tablet;
public drawtool() { System.out.println("meta handler created"); }
public drawtool(String p, String options) {
super(p,"*"); // create empty tablet
init(options);
}
public int operator() {
System.out.println("Calling drawtool:" + _event.args(0) );
String[] argv = _event.argv();
if ("self".equals(argv[1])) tk.result(self().path());
else if ("drawtool".equals(argv[0]))
create(argv[1],_event.args(2));
else if ("path".equals(argv[1])) tk.result(path());
else if ("pack".equals(argv[1])) pack(_event.args(2));
else self().eval( _event.args(1) ); // send through
return OK;
}
void create(String name, String options) {
drawtool m = new drawtool(name,options);
}
void init(String options) {
root = new frame(path(),"-class Meta");
frame frame = new frame(root,"frame");
tablet = new tablet(frame,"tablet",options);
toolbox toolbox = new toolbox(frame,tablet);
menubar menubar = new menubar(root,tablet,toolbox);
toolbox.pack("-side left -fill y -expand 0");
tablet.pack("-side left -fill both -expand 1");
menubar.pack();
frame.pack("-expand 1 -fill both");
redirect( tablet ); // the widget of interest
}
};
Guidelines for design
subsections:
Computing is a relatively young discipline.
Despite its short history, a number of styles
and schools promoting a particular style have emerged.
However, in contrast to other disciplines
such as the fine arts (including architecture)
and musical composition, there is no
well-established tradition of what is to be considered
as good taste with respect to software design.
There is an on-going and somewhat pointless debate as to
whether software design
must be looked at as an art or must be promoted
into a science. See, for example, Development process -- cognitive factors
Design criteria -- natural, flexible, reusable
Individual class design
A class should represent a faithful model of a single concept,
and be a reusable, plug-compatible component
that is robust, well-designed and extensible.
In slide class-design, we list a number of suggestions
put forward by Class design -- guidelines
Inheritance and invariance
When developing complex systems or class libraries,
reliability is of critical importance.
As shown in section contracts,
assertions provide a means by which to check the runtime
consistency of objects.
In particular, assertions may be used to check that
the requirements for behavioral conformance
of derived classes are met.
Invariant properties -- algebraic laws
public:
employee( int n = 0 ) : sal(n) { }
employee* salary(int n) { sal = n; return this; }
virtual long salary() { return sal; }
protected:
int sal;
};
Invariant
Problem -- hidden bonus
public:
long salary() { return sal + 1000; }
};
Invariant
Solution -- explicit bonus
public:
manager* bonus(int n) { sal += n; return this; }
};
Invariant -- restored
An objective sense of style
The guidelines presented by G
Client
Supplier
Acquaintance
Law of Demeter
Class transformations
From specification to implementation
subsections:
Designing an object-oriented system
requires the identification of
object classes and the characterization of
their responsibilities,
preferably by means of contracts.
Structural versus behavioral encapsulation
Object-oriented modeling has clearly been inspired
by or, to be more careful, shows significant
similarity to the method of semantic modeling
that has become popular for developing information systems.
In an amusing paper, Structural versus behavioral encapsulation
semantic model object-oriented abstraction structural behavioral inheritance subtypes subclasses Semantic modeling -- constructing types
Object-oriented modeling
Model-based specification
State and operations
Change and invariance
Verification
State
Operations
Counter
Bounded counter
The specification of a library
State
Operations
Abstract systems and events
User actions may require complex interactions
between the objects constituting
the object model of a system.
Such interactions are often of
an ad hoc character in the sense that they embody one
of the many possible ways in which the functionality
of objects may be used.
What we need is a methodology or paradigm that
allows us to express these interactions in a concise
yet pragmatically amenable way.
In Abstract systems -- design methodology
Events -- high level glue
Example -- the library
Abstract system -- exemplary interface
public:
person* borrower;
book() {}
void borrow( person* p ) { borrower = p; }
void _return( person* p ) { borrower = 0; }
bool inlibrary() { return !borrower; }
};
public:
person() { books = new set example
book* ChandyMisra = new book();
book* Smalltalk80 = new book();
person* Hans = new person();
person* Cees = new person();
Stroustrup->borrow(Hans);
Hans->allocate(Stroustrup);
ChandyMisra->borrow(Cees);
Cees->allocate(ChandyMisra);
Smalltalk80->borrow(Cees);
Cees->allocate(Smalltalk80);
Events
public:
virtual void operator()() = 0;
};
public:
Borrow( person* _p, book* _b ) { _b = b; _p = p; }
void operator()() {
require( _b && _p ); // _b and _p exist
_b->borrow(p);
_p->allocate(b);
}
private:
person* _p; book* _b;
};
public:
Return( person* _p, book* _b ) { _b = b; _p = p; }
void operator()() {
require( _b && _p );
_b->_return(p);
_p->deallocate(b);
}
private:
person* _p; book* _b;
};
Summary
The drawtool application
Guidelines for design
From specification to implementation
Questions
Further reading
[]
readme
course
preface
1
2
3
4
5
6
7
8
9
10
11
12
appendix
lectures
resources
eliens@cs.vu.nl