[comp.lang.eiffel] Object oriented design or 'to inherit or not to inherit'

day@grand.UUCP (Dave Yost) (12/22/88)

From: Jos Warmer <jos@cs.vu.nl>
Date:     Mon, 21 Nov 88 10:24:59 MET

Having read and enjoyed the book 'Oject Oriented Software Construction'
of Bertrand Meyer, I have some questions/remarks about (multiple)
inheritance and safety.

At page 241 of Bertand Meyer's book an alternate definition is
given for STACK2 (page 118): the class FIXED_STACK.  It is declared
an heir of class ARRAY, instead of a client.

As far as I can see this definition of FIXED_STACK has a serious
safety-leak, as opposed to the definition of STACK2 at page 118.

Consider the following piece of (pseudo) eiffel code:

    a : ARRAY[INTEGER];         -- declare an entity of type ARRAY
    f : FIXED_STACK[INTEGER];   -- declare an entity of type FIXED_STACK

    f.Create;        -- created a fixed stack
    f.push(12);      -- top of stack is now 12

    a := f;          -- allowed, because FIXED_STACK is descendant of ARRAY
    a.enter(1, 20);  -- enter is allowed on ARRAY's

    value := f.top:  -- this will deliver value 20, instead of the previously
                     -- entered 12.

This example shows that any client of FIXED_STACK can manipulate its
implementation.
This is clearly not what is intended for the class FIXED_STACK. It
doesn't behave like a stack anymore.  The FIXED_STACK class has no way
to enforce its own semantics.  This is dangerous.
In this case the rule at page 333 should be applied:

    'inheritance means "is", client means "has".'

A FIXED_STACK has an array as it's implementation.  It ** is not **  an ARRAY.
In the discussion at page 333 the safety-aspect of the solution for
FIXED_STACK is not considered.
The disadvantage clearly outperforms two of the advantages mentioned:

The advantage of having a simpler way of accessing the array features
is superficial.
The reason that inheritance is more efficient is not to the point.
Whether inheritance or buying should be used must only depend on
design-criteria, not on efficiency. This attitude is correctly
expressed at page 344:

    "An object-oriented environment in which designers would have to think
     twice before adding an inheritance level because it would mean degraded
     performance would be rather unpleasant."

It is not clear whether the flexibility obtained by inheritance is an
advantage or not.

Also, the book advocates safe programming by the use of pre- and post-
conditions and assertions.  When you think this way, the FIXED_STACK
solution should be considered bad.

Maybe another rule can be formulated, in choosing between inheritance
or buying:

    "If a class B needs class A, then B should not be an heir of A if
     any operation on A corrupts the intended semantics of B.  In these
     cases B should be a client."

This rule can be seen as an extension of the "is-a" versus "has" rule.
Any comments, or different opinions?

				    Jos Warmer
				    jos@cs.vu.nl (via mcvax)

day@grand.UUCP (Dave Yost) (12/22/88)

From: Jan Prins <prins@cs.unc.edu>
Date: Mon, 21 Nov 88 17:01:47 est
In-Reply-To: Jos Warmer's message of Mon, 21 Nov 88 09:18:11 PST <8811211718.AA26128@grand.COM>

Like Jos, I too have recently become acquainted with Eiffel, but I had not 
thought of the subtle ADT implementation-visibility defect he described.

Although I agree with Jos that FIXED_STACK might, in this case, be better
thought of as a "client" of ARRAY rather than a "descendant", the general
problem of unauthorized access to ancestral features of a class remains.
Jos proposes a rule that would serve to prevent inheritance in cases
where the exposed parent-class features could be abused:

    "If a class B needs class A, then B should not be an heir of A if
     any operation on A corrupts the intended semantics of B.  In these
     cases B should be a client."

I would imagine that this rule would effectively prevent most inheritance
in ADT's.  Apart from deferred classes that impart some common structure on
descendants, most inheritance brings something into the class to be used 
in the implementation of the class -- something subject to manipulation
via Jos' scheme.

My opinion is that this problem should be solved with declarations and the 
type system.  Why make rules to recognize an apparent defect (don't inherit 
here -- it's not safe!) rather than fixing it?

For example, one might require that a direct ancestor of a class must be
explicitly exported from a class, if it is desirable that others be able 
to access the ancestor.  Or that the "inherit" clause be partitioned into
a "private" and "public" portion.  Thus class FIXED_STACK would make 
ancestor STACK (a deferred class that defines a few functions common to all 
representations of stacks) visible, but not ancestor class ARRAY.

With such a scheme and declarations

     a : ARRAY[INTEGER];         -- declare an entity of type ARRAY
     f : FIXED_STACK[INTEGER];   -- declare an entity of type FIXED_STACK

the type system refuses the assignment    

     a := f;

since the class of "f" is not a publicly-acknowledged descendant of the 
class of "a".  Consequently manipulation of "f"'s representation via Jos'
device is thwarted.

I suppose that inheritance-structure visibility declarations would be 
transitive.  If  class C publically inherits B and B publically inherits A, 
then, as far as the type system is concerned, features of class A are 
polymorphic with respect to instances of classes A, B and C.

But before I go any further astray, it would be interesting to hear from
others whether Jos' observation is really perceived to be a problem.

Jan Prins
Dept. of Computer Science
UNC - Chapel Hill
prins@cs.unc.edu

day@grand.UUCP (Dave Yost) (12/22/88)

From: Dave Robbins <drobbins@gte.com>
Date: Tue, 22 Nov 88 08:58:31 EST

I think that the "defect" described by Jos is something that illustrates
the difference between inheritance of "specification" and inheritance of
"implementation."  The two are distinctly different concepts.

1) Inheritance of specification is inheritance of behavior, or protocol.
   A class inherits behavior from another class when it intends to behave
   the same way, and in such cases it is clearly expected that the subclass
   will respond appropriately to all the same operations to which the
   superclass responds.

2) Inheritance of implementation is no more and no less than a way to
   implement a class by sharing its implementation with that of another
   class.  A class that inherits for implementation purposes (such as
   FIXED_STACK in Jos' message) has absolutely no intention of behaving
   in all respects like the class from which it inherits.

It is easy to get the two concepts confused, especially when dealing with
languages that make no distinction between the two concepts of inheritance.
Off the top of my head, I don't recall whether or not Meyer in his book
draws such a distinction.  I do think that Eiffel is somewhat schizoid
about this: some of its rules (e.g., those for pre- and post-conditions
on redefined routines) are rather clearly intended to preserve behavior
in subclasses, thus supporting a specification-inheritance view, while
other rules (e.g., permitting a subclass to not export a feature exported
in the superclass) seem more in line with an implementation-inheritance
view.  The problem Jos points out is that FIXED_STACK intends to inherit
the implementation *but not the specification* of ARRAY.

In the class of object-oriented languages exemplified by Smalltalk, there
is no formal notion of specification at all -- inheritance is always
implementation inheritance.  In the class of object-oriented languages
exemplified by Eiffel, inheritance leans toward that of specification,
but retains implementation inheritance as well.  Trellis/Owl is the one
object-oriented language that I am familiar with that treats inheritance
strictly as inheritance of specification.

It is my opinion that the distinction between the two forms of inheritance
is very important, and that languages such as Eiffel must come to grips
with that distinction.  Eiffel has not, at this point, dealt effectively
with that distinction.  Both forms of inheritance are desirable, but the
two must not be inextricably intertwined.  A language must permit
inheritance of specification when that is desired, and inheritance of
implementation *but not specification* when that is desired.  Jan's
suggestion of "private" inheritance may have some merit.

I recall reading at least one or two papers in OOPSLA '87 that address
the two concepts of inheritance.

Enough for now.

Dave Robbins                    GTE Laboratories Incorporated
drobbins@gte.com                40 Sylvan Rd.
...!harvard!bunny!drobbins      Waltham, MA 02254