[comp.sw.components] OOP and re-usable cakes

omahony@swift.cs.tcd.ie (07/31/90)

> There will be a recipe in the library for a plain cake.  If you want to write a
> recipe for a fruit cake, you just write "Proceed as for a plain cake, but add a
> handful of fruit in the final mixing" (this is not a recommendation for my
> cakes!).  You "inherit" the plain cake recipe from the library, and produce a
> descendant recipe.  This will create a different object, and it is the
> _objects_ which are the "components";  they are created at run-time and behave
> according to their class descriptions (i.e. the programs).  You don't make a
> good fruit cake by buying a plain cake from the shop and then trying to stuff
> fruit into it when you get home.

I like this analagy - now suppose you want to make a loaf of bread, or an
omlette.  Even though both of these involve gathering materials together
in a bowl, agitating them and then applying heat (am I making you hungry!)
 - they are not instances of the class 'cake'.

My basic point is that the use of classes/objects as reusable components
suffers from similar (though not as severe) problems to procedure libraries.
The author of the component divides the functionality into a fixed part
(the class decription/ procedure body) and a variable part (the
parameter list/ the inheritance mechanism).  The problem occurs when a
potential (re-) user of the component has an application for about 90%
of the fixed part.

Comments?

ogden@seal.cis.ohio-state.edu (William F Ogden) (08/01/90)

In article <7057.26b57e9c@swift.cs.tcd.ie> omahony@swift.cs.tcd.ie writes:
>>There will be a recipe in the library for a plain cake.If you want to write a
>>recipe for a fruit cake, you just write "Proceed as for a plain cake, but add
>>a handful of fruit in the final mixing" (this is not a recommendation for my

    ...
>I like this analagy - now suppose you want to make a loaf of bread, or an
>omlette.  Even though both of these involve gathering materials together
>in a bowl, agitating them and then applying heat (am I making you hungry!)
> - they are not instances of the class 'cake'.
>
>My basic point is that the use of classes/objects as reusable components
>suffers from similar (though not as severe) problems to procedure libraries.
  ...
>Comments?

Actually there is a good example of reusability lurking here, but not in
the recipes. The designer of kitchen equipment looks at the omelet, bread,
cake, etc. problem and notices that they all involve agitating ingredients
in a bowl. The potential for reusability is clear (and vital to marketing,
since cooks don't really want a million gadgets in their kitchens) and
thus the mixer is born.
Similarly, the `applying heat' problem is solved by the highly reusable
stove. The containment of ingredients while heating problem has special
features that lead to the less reusable bread pan, cake pan, frying pan,
etc. 
An important point to note here is the independence of the reusable
components. You don't inherit a stove with a built-in bread pan,
notice that it's 90% appropriate for the cake heating problem, and
modify it to create a stove with a built-in cake pan. Moreover,
you rarely find a stove with a built-in mixer.
Another point to note is that recipes respect the integrity of reusable
components. They don't ask the cook to reshape the blades in the mixer or
cut a small hole in the oven door.

/Bill

rick@tetrauk.UUCP (Rick Jones) (08/03/90)

In article <7057.26b57e9c@swift.cs.tcd.ie> omahony@swift.cs.tcd.ie writes:
> [I wrote]:
>> There will be a recipe in the library for a plain cake.  If you want to write a
>> recipe for a fruit cake, you just write "Proceed as for a plain cake, but add a
>> handful of fruit in the final mixing" (this is not a recommendation for my
>> cakes!).  You "inherit" the plain cake recipe from the library, and produce a
>> descendant recipe.  This will create a different object, and it is the
>> _objects_ which are the "components";  they are created at run-time and behave
>> according to their class descriptions (i.e. the programs).  You don't make a
>> good fruit cake by buying a plain cake from the shop and then trying to stuff
>> fruit into it when you get home.
>
>I like this analagy - now suppose you want to make a loaf of bread, or an
>omlette.  Even though both of these involve gathering materials together
>in a bowl, agitating them and then applying heat (am I making you hungry!)
> - they are not instances of the class 'cake'.
>
>My basic point is that the use of classes/objects as reusable components
>suffers from similar (though not as severe) problems to procedure libraries.
>The author of the component divides the functionality into a fixed part
>(the class decription/ procedure body) and a variable part (the
>parameter list/ the inheritance mechanism).  The problem occurs when a
>potential (re-) user of the component has an application for about 90%
>of the fixed part.

There are two problems here.  The last paragraph suggests that the author of a
component decides what may inherit from it, and how.  In other words the scope
of re-use is being fixed in the design of the component.  Although some
object-oriented languages encourage this viewpoint (C++ being one of the worst
offenders, IMHO), for re-use to be valuable in the long-term classes need to be
re-usable in ways which the original author never envisaged.

The second problem gets back to an issue I mentioned earlier in my previous
article, i.e.  what is inheritance?  The nice theoretical, text-book concept
says that the sub-class has all its parent's properties, plus some more.  Thus
an object of the sub-class must be prepared to do everything its parent can do
- it can't refuse to inherit some of its parent's behaviour.  This means the
object exhibits total conformance to its parent, and allows a compiler to treat
classes and types as synonymous making type conformance checking nice and easy.

This approach doesn't always reflect reality, though.  You often do want an
inheritance which says "A is like B except ...", meaning the sub-class is NOT
going to conform to some aspects of its parent.  An example I was quoted
recently was the case of a square inheriting from a rectangle;  not many people
would argue with that concept.  But suppose the rectangle supports an
"elongate" operation which changes its aspect ratio - you can't do that to a
square without changing it back into a rectangle.  The implication is partial
inheritance.  The result is that the class inheritance tree and the type
conformance tree are not the same, and static type checking in a compiler
becomes a whole new ball game.

The only convincing statement I've seen which seriously addresses this issue is
a recent re-posting by Bertrand Meyer in comp.lang.eiffel of a paper originally
written about a year ago.  The article was posted in two parts because of size,
and the references are <385@eiffel.uucp> & <386@eiffel.uucp>.  There is also an
article which covers the Eiffel type system in general, ref. <387@eiffel.uucp>.

I won't waste time & bandwidth quoting from these articles here, If you're
interested in this problem, I seriously suggest you read them.

-- 
Rick Jones					You gotta stand for something
Tetra Ltd.  Maidenhead, Berks			Or you'll fall for anything
rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick)	     - John Cougar Mellencamp

rick@tetrauk.UUCP (Rick Jones) (08/03/90)

In article <82593@tut.cis.ohio-state.edu> William F Ogden <ogden@cis.ohio-state.edu> writes:
> [the OOP/recipe analogy, etc ... ]
>
>Actually there is a good example of reusability lurking here, but not in
>the recipes. The designer of kitchen equipment looks at the omelet, bread,
>cake, etc. problem and notices that they all involve agitating ingredients
>in a bowl. The potential for reusability is clear and thus the mixer is born.
>Similarly, the `applying heat' problem is solved by the highly reusable
>stove. etc.

Perhaps the re-usable kitchen equipment which turns the recipes into food is
more analagous to the compiler which mixes up the program (I chose the words
carefully!) and the computer which cooks (executes) it for the user to eat
(use).  After all, compilers and computers are exceedingly re-usable, to
mis-quote Mr. Kipling.


-- 
Rick Jones					You gotta stand for something
Tetra Ltd.  Maidenhead, Berks			Or you'll fall for anything
rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick)	     - John Cougar Mellencamp

steve@taumet.com (Stephen Clamage) (08/05/90)

rick@tetrauk.UUCP (Rick Jones) writes:

>There are two problems here.  The last paragraph suggests that the author of a
>component decides what may inherit from it, and how.  In other words the scope
>of re-use is being fixed in the design of the component.  Although some
>object-oriented languages encourage this viewpoint (C++ being one of the worst
>offenders, IMHO), for re-use to be valuable in the long-term classes need to be
>re-usable in ways which the original author never envisaged.

I'm not sure of what you are accusing C++ here.  It is possible to define
a C++ class in such a way that it is hard to re-use, but it is not
necessary to do so.  Using C++ terminology: Some languages, such as Eiffel,
make all member functions virtual, and so can be adjusted for any derived
class, whatever the original desinger envisioned.  In C++ you may make
all functions virtual if you wish.  If run-time efficiency is more important,
you can eliminate the overhead of virtual function calls.  A later user
may acquire the source code, change declarations in just one place per
function to add the keyword "virtual", and recompile.

>an object of the sub-class must be prepared to do everything its parent can do
>- it can't refuse to inherit some of its parent's behaviour.  This means the
>object exhibits total conformance to its parent...
>classes and types as synonymous making type conformance checking nice and easy.
>This approach doesn't always reflect reality, though.  You often do want an
>inheritance which says "A is like B except ...", meaning the sub-class is NOT
>going to conform to some aspects of its parent....

Again, you can do this in C++.  It is possible to eliminate some of a
base (parent) class's functionality within a derived (sub) class.  You
simply override the base class function in the derived class with an
empty function which is private.

While I understand your example, I don't agree it is good design.  You
have an "extend" operation for a rectangle which changes its aspect
ratio, something not possible for a square.  I suggest instead something
like this inheritance graph:

			quadrilateral
			______|______
			|           |
		rectangle	trapezoid
		____|____
		|       |
extensible_rectangle	square

Now there is no need to suppress a parent's functionality.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com

dl@g.g.oswego.edu (Doug Lea) (08/05/90)

> From: steve@taumet.com (Stephen Clamage)

> >rick@tetrauk.UUCP (Rick Jones) writes:

> >This approach doesn't always reflect reality, though.  You often do want an
> >inheritance which says "A is like B except ...", meaning the sub-class is NOT
> >going to conform to some aspects of its parent....
> 
> Again, you can do this in C++.  It is possible to eliminate some of a
> base (parent) class's functionality within a derived (sub) class.  You
> simply override the base class function in the derived class with an
> empty function which is private.

This is a TERRIBLE thing to do in any OOPL (like C++ and Eiffel) that
attempts to maintain some relation between subclassing and subtyping.
As Rick Jones says, it breaks conformance: If some base class Base
contains some method m that returns an int, perhaps with some promises
(postconditions) about the nature of the return value and/or the
resulting state of the object, and a derived class Derived redefines m
to simply abort execution or somesuch, then any polymorphic function
accepting argument b of type Base or subclass thereof, and calls b.m
may no longer be able to meet its own postconditions, since its
implicit preconditions (i.e., that b is an object containing method m
with specified behavior) are broken.

Rick is right that programmers do often wish to create a new class
B that is like some existing class A, except that it does not contain
one or more methods/features. Probably the most common case of this
is during the honorable activity of `retrospective abstraction', where
you find that a class was overdesigned and you need something with
fewer features; or that two previously unrelated classes could benefit
by being linked via a new common ancestor.

For among the simplest (perhaps least realistic) examples, suppose you
HAVE a Deque class lying around, but you NEED a simple Stack class.
The Deque contains methods push, pop, empty, rearpush, and rearpop.
For both safety and coherence reasons, you don't want to just use the
Deque as a Stack (since you want to guarantee that pushrear is NEVER
called). But if you create class Stack as a subclass of Deque by
nulling out rearpush and rearpop, you open up the Stack component to
misuse: Sending a Stack to any function expecting a Deque might result
in unexpected behavior like a core dump.

In fact, it is easy to see that Stack is a SUPERclass of Deque. A
Deque may be safely used wherever a Stack is expected. No `common'
OOPLs allow you to RETROSPECTIVELY state this in any way short of
redesigning the heirarchy. 

However, support for such constructs is not a completely novel idea:
Retrospective superclassing is nearly the same notion as `views' in
OBJ (see any of Goguen's recent papers) and LILEANNA (an Ada
tool/preprocessor described by W. Tracz in his in-progress thesis) and
`reductions' in Alberich (see July '90 SIGPLAN notices article by
Paaki et al). It is also possible to get this effect in languages
based on implicit conformance-checking rather than explicit
subtyping/subclassing, e.g., Quest (see Cardelli's DEC-SRC TRs), and
to some extent, Emerald/Jade (see Raj & Levy's ECOOP '89 paper).

What might an OOPL supporting `views' (I'll stick to that term) look like?

First, and most importantly, it MUST allow (even nicer: mandate) the
separation of type specification (i.e., declaration of method
argument and return types, perhaps with pre- and post-conditions) from
class implementation (i.e., member data and code needed to carry out
these specs). 

It is possible to maintain this separation via the convention of using
`Abstract' or `Deferred' classes in C++, Eiffel, and other languages,
or via more direct support found in Johnson's Typed Smalltalk (TS) and
Emerald.

This separation is necessary in order to ensure that a new view is
properly self-contained. For example, if the Deque class, above, mixed
specification and implementation, it might be the case that
Deque::push was implemented by calling Deque::rearpush when the Deque
is empty. A Stack view of the Deque that omits rearpush would then
fail to even make sense.

The consensus these days seems to be that such separation is a Good
Thing anyway: it allows specifications to be realized via multiple
implementations with different performance characteristics; it allows
independent reuse of either specifications or implementations; and
allows independent extensions of both types and implementations.

However, separation of specification and implementation can make it
hard to reuse implementations without further aids. A composition
mechanism similar to that of Emerald/Jade that is equally useful for
both composing and splitting apart implementations would be a valuable
adjunct.  Alternatively, the implementation side might even employ a
form of prototyping/delegation (as in SELF) instead of subclassing or
composition: Separating specifications from implementations allows the
design and coding tools/constructs to vary independently.

There are a number of other interesting issues as well. Among them:

*) Renaming: Suppose the Deque method to place things on the front
    was called `frontpush', but you'd like it to be called just `push'
    in the Stack view. Is this OK?

*) Multiple views: A Deque can be view'd as a Stack in either of 
    two ways (using the Deque push/pop pair OR the rearpush/rearpop 
    pair to perform Stack push/pop). Can more than one view be supported?

*) Structure: Programs written with views might be hard to read since 
    new superclasses can be declared on the fly. Tools are needed to
    reconstruct/fake the underlying hierarchical design.

*) And a host of implementation matters ...

Are views important enough constructs to warrant creation of new
languages or tools? (All of the above COULD be incorporated into a
front-end design tool/preprocessor for C++, Smalltalk, SELF or nearly
any other OOPL.)

-Doug
--
Doug Lea, Computer Science Dept., SUNY Oswego, Oswego, NY, 13126 (315)341-2688
email: dl@g.oswego.edu            or dl@cat.syr.edu
UUCP :...cornell!devvax!oswego!dl or ...rutgers!sunybcs!oswego!dl

rick@tetrauk.UUCP (Rick Jones) (08/06/90)

In article <382@taumet.com> steve@taumet.com (Stephen Clamage) writes:
>rick@tetrauk.UUCP (Rick Jones) writes:
>
>>The scope of re-use is being fixed in the design of the component.  Although some
>>object-oriented languages encourage this viewpoint (C++ being one of the worst
>>offenders, IMHO), for re-use to be valuable in the long-term classes need to be
>>re-usable in ways which the original author never envisaged.
>
>I'm not sure of what you are accusing C++ here.

Sorry! I'm not trying to start an object (class?) war.  Ultimately, anything is
possible in any language, but I do believe that the characteristics of a
language influence the design of the software;  in fact, I take the view that
coding is just the final step in the design process.  Yes, you _can_ make
everything virtual, but how many C++ programmers actually _do_, in anticipation
of something they haven't yet thought of?  This isn't to say I'm accusing C++
programmers of being wrong, per se, either.  Perhaps it's just human nature to
use a tool in the most obvious way which the tool itself suggests;  if you make
a corkscrew in the shape of a hammer, someone is bound to open a bottle by
smashing the top off!  (this isn't a good analogy, but it was off the top of my
head).

>>This approach doesn't always reflect reality, though.  You often do want an
>>inheritance which says "A is like B except ...", meaning the sub-class is NOT
>>going to conform to some aspects of its parent....
>
>Again, you can do this in C++.  It is possible to eliminate some of a
>base (parent) class's functionality within a derived (sub) class.  You
>simply override the base class function in the derived class with an
>empty function which is private.

But it's not that simple if you are going to use polymorphic references, which
for me is what an effective class-based design is all about.  If you assign an
object of the sub-type to a pointer variable of the parent type, the compiler
will assume that all the parent's functionality is available on all objects
which may be attached to it.

>While I understand your example, I don't agree it is good design.  You
>have an "extend" operation for a rectangle which changes its aspect
>ratio, something not possible for a square.  I suggest instead something
>like this inheritance graph:
>
>			quadrilateral
>			______|______
>			|           |
>		rectangle	trapezoid
>		____|____
>		|       |
>extensible_rectangle	square

The example is certainly contrived, but your solution illustrates my point.
Suppose the inheritance tree was already in place, without square and with a
single (extensible) rectangle, and your task was to implement square.  Your
solution requires a re-arrangement of the existing hierarchy.  Although
ultimately desirable, it is not helpful if it has to be done in order to solve
the immediate problem - it's making re-use more difficult.  Good re-use is
achieved by finding and implementing the "nearest fit" to what is already
there, even if not perfect.  The ability subsequently to re-arrange parts of
the hierarchy without having to change things which derive from them is equally
important.

There will be many real problems of this type which are far less trivial.

(Ducking flames: holiday period 3 weeks, expiry period 2 weeks :-)

-- 
Rick Jones					You gotta stand for something
Tetra Ltd.  Maidenhead, Berks			Or you'll fall for anything
rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick)	     - John Cougar Mellencamp