[comp.lang.ada] Re^2: Integrating concurrent & O-O programming

oscar@cui.unige.ch (NIERSTRASZ Oscar) (11/23/89)

tma@osc.COM (Tim Atkins) writes:

>In article <7062@hubcap.clemson.edu> wtwolfe@hubcap.clemson.edu (Bill Wolfe) writes:
>> ...consider the interference that occurs between class inheritance 
>> and encapsulation when subclasses are allowed to access freely the 
>> instance variables of the parent class [Sny86].  In this case we may 
>> say that support for inheritance diminishes the degree of encapsulation
>> that was achieved without inheritance...

>I have heard this before and, frankly, I don't get it.  The subclass 
>has inherited data structure definition from the parent class.  Therefore
>its instances will contain instances of the parent data structure.  This
>is totally local to the object!  Only the definition was inherited.  Why
>is this a bad thing?  Why does it break encapsulation?  I understood the
>encapsulation wrt data provided by OO techniques to simply state that instance
>data should not be accessed directly but only through a method defined on
>the class.  Therefore, inheritance does not seem to break encapsulation.

>I would appreciate it if someone could set me straight here.

At the level of objects, encapsulation protects the client of an object
from needing to know details of the object's representation.  One simply
"sends messages", i.e., there is a well-defined interface between the
client and the object.  Modifications to the representation should not affect
the client (the service may change somewhat, but the client's code is
still valid).

In object-oriented languages, there are (at least) *two* clients to
object classes: the clients of the object instances, and subclasses
that wish to inherit the code.  If you view inheritance as purely a
machanism for share code and interfaces within a collection of object
classes (i.e., if you are a lone programmer) then there is no reason
why subclasses should not see everything they inherit.  But if you are
implementing a class library for use by other programmers, or if you
wish yourself to make use (reuse) by inheritance of existing classes,
you would like to protect subclasses from modifications made to
superclasses.  That is why some languages permit subclasses to make
use only of instance variables and methods explicitly exported.

What aggravates the situation is that in order to implement useful
subclasses, you often *want* subclass instances to access (hidden)
inherited instance variables and private methods.  This means that either:
1. the implementor of a subclass must completely understand not only
	the source code of the superclass, but also everything inherited
	by the superclass from *its* superclasses etc.
	(deciphering the "yoyo" effect in the presence of self and
	super can be like trying to read a program loaded with gotos)
or:
2. the interface of the superclass to inheriting subclasses must be
	specified as completely as the interface of an object to
	its clients.
We would prefer solution 2, but unfortunately we don't know how to do this
very well, although we try sometimes to make do with natural language.
("If you inherit from this class, make sure you don't modify variable x,
unless you ...")

Finally, when we have "concurrent objects", i.e., objects that make
use of concurrency and synchronization mechanisms in their methods,
subclasses inheriting such code must be careful about how they
extend the superclass behaviour to avoid nasty surprises.

Our experience in designing and implementing a concurrent object-oriented
language ("Hybrid" -- see OOPSLA '87) revealed that concurrency mechanisms
that seem quite reasonable at the object level, can pose difficulties
in the presence of inheritance and composition (e.g., using a concurrent
object as an instance variable can create unexpected results).  Although
the conflict between concurrency mechanisms and object-oriented features
such as inheritance has been noted by many researchers, clean integration
is still an open problem.

Oscar Nierstrasz

---------------------------------------------------------------------
Dr. O.M. Nierstrasz                     E-mail: oscar@cuisun.unige.ch
Centre Universitaire d'Informatique     Tel:    41 (22) 787.65.80
12 rue du Lac, CH-1207 Geneva           Fax:    41 (22) 735.39.05
SWITZERLAND                             Home:   41 (22) 733.95.68
---------------------------------------------------------------------

anders@cui.unige.ch (BJORNERSTEDT Anders) (11/24/89)

In article <477@cui.unige.ch> oscar@cui.unige.ch (NIERSTRASZ Oscar) writes:

>In object-oriented languages, there are (at least) *two* clients to
>object classes: the clients of the object instances, and subclasses
>that wish to inherit the code.  If you view inheritance as purely a

Another interface is the interface to other instances of the same class.
Try implementing the class integer with a method add in Smalltalk
without exposing your internal representation.

You can actually distinguish four different interfaces:

1- The interface to self of the same class. No encapsulation wanted here :-)

2- The interface to self of some other (super)class.

3- The interface to some other instance of the same class.

4- The interface to some other instance of some other class.

In the programming language PAL of the Avance prototype (see oopsla88)
we made no distinction between 2 and 3 and had three kinds of operations.
Public operations could be used by 1-4.
Private operations could be used by 1-3.
Hidden operations and instance variables could be used by 1.

The concept of a "friend functions" in C++ is related to this and further
subdivides 4. [I am not a c++ expert though so I may be wrong].

>2. the interface of the superclass to inheriting subclasses must be
>	specified as completely as the interface of an object to
>	its clients.
>We would prefer solution 2, but unfortunately we don't know how to do this
>very well, although we try sometimes to make do with natural language.

Kind 2 operations give you exactly this.

Kind 3 operations allow you to implement an "algebra" without exposing the
internals of objects.

If you have a distributed system then the distinction of kind 2 and 3 
operations becomes important for another reason. Directly accessing
non-local state may not be practical. Some people argue that inheritance
is not useful or even "inconsistent" with distribution. Kind 2 operations
at least makes inheritance "consistent" with distribution. Whether it
is useful can be debated. Still if you drop inheritance from the object
model of a distributed system, operations of kind 3 become important.
For further arguments on this last issue you can read the article on 
Emerald in IEEE transactions on software engineering January 1987.

---------------------------------------------------------------------
Anders Bjornerstedt			E-mail: anders@cuisun.unige.ch
Centre Universitaire d'Informatique             
12 rue du Lac, CH-1207 Geneva
---------------------------------------------------------------------
Tel:    41 (22) 787.65.80-87             Fax:    41 (22) 735.39.05
Home:   41 (22) 735.00.03                Telex:  423 801 UNI CH
---------------------------------------------------------------------

sakkinen@tukki.jyu.fi (Markku Sakkinen) (11/26/89)

In article <481@cui.unige.ch> anders@cuisun.unige.ch writes:
>In article <477@cui.unige.ch> oscar@cui.unige.ch (NIERSTRASZ Oscar) writes:
>
>>In object-oriented languages, there are (at least) *two* clients to
>>object classes: the clients of the object instances, and subclasses
>>that wish to inherit the code.  If you view inheritance as purely a
>
>Another interface is the interface to other instances of the same class.
>Try implementing the class integer with a method add in Smalltalk
>without exposing your internal representation.
>
>You can actually distinguish four different interfaces:
>
>1- The interface to self of the same class. No encapsulation wanted here :-)
>
>2- The interface to self of some other (super)class.
>
>3- The interface to some other instance of the same class.
>
>4- The interface to some other instance of some other class.
>
>In the programming language PAL of the Avance prototype (see oopsla88)
>we made no distinction between 2 and 3 and had three kinds of operations.
>Public operations could be used by 1-4.
>Private operations could be used by 1-3.
>Hidden operations and instance variables could be used by 1.

I think this is very important and interesting (because I have myself
thought about these matters :-) ).
Note that Smalltalk-80 (TM) and some other OOPL's make no difference
between 3 and 4 nor between 1 and 2,
while perhaps the majority of OOPL's (as well as CLU, Modula-2,
and Ada(R) - for which case 2 is vacuous because they have no inheritance)
make no difference between 1 and 3.

Case 2 can be further subdivided on the basis of whether the other
class is a _sub_ or _super_ class of the class in question.
(In many models of multiple inheritance, classes may even get
indirectly connected via a common subclass.)
Rather many OOPL's allow a class designer to specify that some features
shall not be visible to _sub_classes, but I can't remember reading about any
language that allows the same toward _super_classes.

>The concept of a "friend functions" in C++ is related to this and further
>subdivides 4. [I am not a c++ expert though so I may be wrong].

No, you are right.

> [...]

Markku Sakkinen
Department of Computer Science
University of Jyvaskyla (a's with umlauts)
Seminaarinkatu 15
SF-40100 Jyvaskyla (umlauts again)
Finland