Some quick thoughts on the "Java Tip 68" web page that Rick pointed us to ...
Two things seem to be being discussed in this article/pattern. One is
a notion of (very strong) decoupling between components and the other
is the avoidance of large switch-statements.
DECOUPLING
==========
As the summary says, registering a callback interface (or "function"
in C terms) with the caller decouples the caller from knowing anything
about who it is calling ... other than it implements that callback
interface (or function) ... which is fine. This enables the caller
to issue the the call without really knowing who it is calling (other
than having a pointer to it) or what it will do with that call.
However, registering the callee with the caller does require the callee
having to see the caller ... or at least to see something that implements the
relevant caller-registration interface. OK, not too bad.
A bigger however, however, is that callback mechanisms give absolutely
no protection in the case when caller and callee are in different
threads of control. To sort that out, we have to get to grips with
synchronized methods ... else suffer race hazards if callee items are
changed by the call. Even then, there is no way the callee can *prevent*
the caller from making a call when it's not wanted ... so the callee
has to accept the call and get involved with wait/notify semantics to
undo the mess ... and that's real hard.
The problem of caller/callee being in separate threads is not addressed
in this article.
But the channel interface of CSP processes gives really very good
decoupling indeed. Here, neither process knows anything about the
other one ... not its name nor any "methods" it might possess. All
each process sees are its channels and that's all it needs. Each
process works to its own (channel) interface, meeting a specification
defined entirely in terms of that interface. The network builder
sees only the (channel) interfaces of the processes, declares the
necessary channels and plugs things together. She is constrained
by only two matters: at a low level, are the channel connections
compatible (i.e. input-to-output only and matching protocols) and,
at a high level, does it make sense to plug those components together
(given their specification which, recall, is in terms only of their
channel interfaces).
Of course, channel interfaces also give you protection when caller and
callee are in different threads. Actually, that's the only way we
can use them!
Active processes interacting via channel messages gives very strong
decoupling between components. It's the model we are used to from
real life. An (electric) kettle is a component with a channel
interface - plug its cord into the wall socket and it comes alive!
AVOIDANCE OF SWITCH STATEMENTS
==============================
The article says: `Usage of switch statements during coding is a sign
of bad design during the design phase of an object-oreiented project'.
Did I miss something? Has there been an article titled: "Switch
Statements Considered Harmful"? This article continues: `Commands
represent an object-oriented way to support transactions and can
be used to solve this [switch-statement] design problem'. Well,
their Command pattern certainly uses lots of Objects so I guess
that makes it `object-oriented' ... but I'm not at all sure of
the taken-for-granted assumption: "if it's object-oriented, it must
be good".
Can someone say why switch-statements are bad? Surely, it's not
a sudden worry about run-time efficiency -- a well-optimised
switch-statement compiles to a computed goto ... which has linear
execution time ... the same as following a pointer to find the code??
A switch-statement is very easy to understand (maybe you had better
`break' at the end of each component though). I like all those
long CASE-inputs in my occam programs ;-)
Heving said that though, there are some benefits from the ideas in
this article. The avoidance of switch statements, though, would not
be my major concern.
The JCSP library has (CSP) processes wrapping up all the java.awt
components. GUI events translate into channel communications from
these processes. Configuring a component is done by sending it
a message down its configuration channel.
A problem is that there are so many possible configuration commands
to AWT components. We could have defined a lengthy (occam-like)
variant protocol class which the configuring process instantiates
and sends to the component. A process in the component interprets
this protocol with the equivalent of a CASE-input - i.e. a long
switch.
The problem is that this protocol would have to be *very* long and,
to support Swing and track updates, would get longer and longer.
It's this length and maintenance prooblem that's the problem.
So, instead, JCSP gives some special protocols for common configurations
(e.g. send a Boolean.TRUE or Boolean.FALSE to enable/disable a Button
... or a String to reset the label on a Button) and a catch-all that
lets you do anything else. This catch-all uses a trick described in
this paper and it's neat (and `object-oriented' ... oh dear).
The jcsp.awt.ActiveButton class contains an interface:
Interface Configure {
public void configure (java.awt.Button button)
}
A process that wants to do some specialised configuration on an instance
of jcsp.awt.ActiveButton creates an instanec of a class that implements
the above. With anonymous inner classes, this can be done in-line:
.
.
.
ActiveButton.Configure conf = new ActiveButton.Configure {
public void configure (java.awt.Button button) {
button.setBackground (...);
button.setFont (...);
... etc. [use whatever java.awt.Button methods you like]
}
}
Then, all you do is send this `conf' down the channel to the ActiveButton
you want so configured. The ActiveButton interprets such a message
by invoking its configure method, passing itself (`this') as the argument.
Since ActiveButton is a sub-class of java.awt.Button, it all works.
This way, completely general configuration of all GUI components is
supported without us having to define a protocol and interpret each
possible variant. We support the common variants directly, because
creating one of these Configure instances is a bit of a drag if you
want to do it a lot.
[Note: the above is in the still-unreleased coming-real-soon-now
version of JCSP ... ]
SUMMARY
=======
CSP channels give you natural decoupling, and consequently good reusability
and maintanability, of components.
Sending on-the-fly created objects implementing general configuration
(or execution) interfaces is a fairly powerful idea and fits perfectly
with channel communicating processes. It save us having to write lots
of code and documenting it ... that, rather than the avoidance of a
lengthy switch-statement, is the benefit.
Peter Welch.