[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Inheritance and active objects

Dear java-threads and occam-com,  [with apologies to those on both lists]

I've been thinking about (OO-)inheritance and how it relates to the active
objects we have been talking about for Java.  I'm worried that:

  o we can have passive objects and inheritance or ...
  o we can have active objects and `proper' object-orientation but ...

we can't have both!  If this is true, which would you chose?

Recapping: passive objects encapsulate data and `methods'.  The behaviour of
the object is determined by its methods.  The methods are just procedures (or
functions) that are called by other objects.  Implementation of those methods,
therefore, has to be caller-oriented and not object-oriented.  This means that
the algorithmic structures within the methods do not parallel the behavioural
specification of the object and that they are hard to design and prove.

Whereas: active objects also encapsulate data and their managing algorithms.
These algorithms are expressed through local flow(s) of control and directly
reflect the behavioural specification of the object -- i.e. the algorithms are
`object-oriented'.  This makes for simplicity.  Active objects are networked
via an interfacing medium constructed from passive objects belonging to a
(hopefully) small set of classes (such as channels, buffers, semaphores,
events, buckets, ...).

Now, objects are instances of classes and classes are related to each other
by a sub-typing relation called `inheritance'.  An object of a sub-type class
is always a member of its super-type class.  This means that if all elements
of the super-type are acceptable in some context, then any elements of the
sub-type will also be acceptable.

In OO-languages, the sub-type class inherits all the attributes from the
super-type.  These attributes are the data and methods of the super-type.
The sub-type can add extra data and methods (i.e. become more complex, which
is somewhat counter-intuitive) and can re-implement (`override') some of
the inherited methods.  The sub-type cannot dispense with any inherited data
fields or methods, since that would break the sub-typing relationship with
the super-type.

For passive objects, adding new data fields and new methods and changing
existing methods is straightforward.  The methods are a set of procedures
that stand on their own.  Their callability does not depend on the state
of their objects -- they can always be called (which leads to trouble when
another object calls a method and discovers that the method's object's state
is not fit for the method to have been called ... life being what it is,
this tends to happen quite a lot).

But sub-typing inheritance on active objects is not straightforward!  Consider
an active object with a channel interface.  The responsiveness of that object
to one its channels does depend on its state.  Also, the active object may
choose to be responsive to a channel at many points in its life (i.e. at many
points in the thread of control in its algorithm that directly -- and in an
object-oriented manner -- implements its life).

It doesn't make sense to talk about `overriding' the response to that
channel (in the way we can `override' the implementation of a stand-alone
method).  Which particular response to the channel is to be overridden ...
one, some or all of them?  It doesn't make sense to talk about about `adding'
extra channels (or whatever) to the interface: where (and in how many places)
do we add the response to the extra channel?  Adding a response for a new
channel (or changing an existing response) has impact on the way the object
responds to its other channels.  The semantics of the whole object has been
changed and -- ouch -- the whole implementation will have to be re-verified.

In a passive object, there is no life in the object outside its method calls.
So, adding an extra method need have no impact on the semantics of existing
methods.  Hence, the claimed benefits of reuse (of the existing methods)
arising from inheritance.  However, I would not take it for granted that
exisiting method semantics are preserved through inheritance and would like
to be reassured that this really is the case!

[Note: an active object can have a restricted design so that it mimics
a passive one.  For example, an occam3 MODULE TYPE implemented by a SERVER,
none of whose guards contain pre-conditions and none of whose guard bodies
contain channel operations associated with the other channel guards.  Such
objects are just the same as passive ones and could be part of an inheritance
tree.  But that's quite a restriction and we do lose the whole power and
simplicity resulting from truely active processes.]

In the Java binding for active objects, the life of the object is expressed
entirly within the `run' method.  So far as inheritance is concerned, we
either re-use the `run' method intact or replace it entirely.  Its interface
is *not* represented by a set of methods that can be changed one-by-one.
Its interface is represented by the parameters given to its `constructor'.

So, what is inheritance for?  Is it to enable reuse of code through the
gradual refinement of behaviour -- the adding of new features and the
modification/specialisation of old ones?  If the latter, there may be another
way to do it than through sub-typing.  For active components, we don't seem
to have the choice of sub-typing ...

Active components have very clean interfaces and they beg to be reused intact.
Behavioural changes can be made -- incrementally -- by composing active objects
together without changing any sub-components.  For example, the dining
philosphers' college can be attached unchanged to a variety of animation
components to give us a variety of exciting behaviours -- from a scrolling
teletype display through to interactive total immersion 3-D virtual reality
with quadrophonic sound effects.  We don't need inheritance for this; we just
need the right components and the right kind of glue for composing them.
Their semantics compose nicely in line with the evolving design.  Reused
components need no re-verification because they are reused in one piece and
because their semantics are independent of the context of that reuse.

Where `inheritance' (of a different kind) may be useful is for inheriting
interfaces.  It would be very nice to have an algebra for constructing
new interfaces from old, without having to write out laboriously all the
old bits we wish to reuse unchanged.  It should be possible to eliminate
bits of exisiting interfaces that we don't need.  We need to be able to
take (disjoint) unions of interfaces (being careful about name clashes),
as well as intersections and subtractions.

Java already has some of this with its IMPLEMENTS mechanism.  Adding an
INTERFACE algebra to occam (or our active Java objects) may be *much* more
significant for re-use than worrying about the absence of an `inheritance'
mechanism.  The result is not a sub-typing relation, but so what?

But, what do others think ... ?