[comp.lang.c++] Inheritance vs. Composition

davidm@uunet.UU.NET (David S. Masterson) (02/11/90)

I'm curious as to the reasons that one might use inheritance (derivation of a
new object from the definition of an old object) and the reasons that one
might use composition (wrapping multiple objects in another object).  I have
been getting definitions around my work area that tend to conflict with my own
views and I'd like to build a body of evidence one way or the other in order
to help make my own views understood.  Anyone care to provide their
definitions with examples as to how to apply their definitions?

--
===================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mt. View, CA  94043
===================================================================
"If someone thinks they know what I said, then I didn't say it!"

ark@alice.UUCP (Andrew Koenig) (02/12/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>, cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

> I'm curious as to the reasons that one might use inheritance (derivation of a
> new object from the definition of an old object) and the reasons that one
> might use composition (wrapping multiple objects in another object).

The key difference in C++ is that if class D inherits [publicly] from
class B, then you can pass a D argument to any function that takes
a B parameter.  Another way of looking at it is that if you derive
D from B, then a D object does [at least] everything a B object does.
If you define a class D with a B member, then you must define every
operation you want a D to be able to perform, even if that is replicating
many of the B operations.
-- 
				--Andrew Koenig
				  ark@europa.att.com

drich@klaatu.lanl.gov (David Rich) (02/12/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>
 cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

   I'm curious as to the reasons that one might use inheritance (derivation of a
   new object from the definition of an old object) and the reasons that one
   might use composition (wrapping multiple objects in another object).

   David Masterson
   uunet!cimshop!davidm

When I make use of "inheritance" as you've defined it, I'm typically
creating an "is-a" relationship between the old and new object.  In
other words, I want the new object to behave like the old object plus
some additional functionality, etc.

When I think of "composition", I think of two kinds: (1) composition
via multiple inheritance and (2) composition via encapsulation, or
ownership (for lack of a better term).  When I make use of (1), I'm
simply applying the "is-a" relationship multiple times.  When I do
this, I know that I want the composite object to be able to have
direct access to all the attributes and methods of its ancestors.
However, when the composite object shouldn't really have direct access
to the attributes and methods of its ancestors, each ancestor becomes
an attribute of the composite object (in the form of an object reference).
In other words, the composite object needs the functionality, but the
relationship is not an "is-a" type.  Here's an example from a
simulation project that I'm working on.  I've built up a map object
that represents an interface to terrain data such as relief,
vegetation, mobility, roads, rivers, etc.  The map encapsulates this
information and provides methods that will allow objects to query the
state of the terrain, generate routes to move along, etc.  The
question that came up was how to give this functionality to objects
that need to move around on terrain.  Using multiple inheritance to
give a truck, for example, the ability (among other things) to
construct movement routes over terrain doesn't seem right to me.  The
inheritance mechanism gives the truck access to information that it
really shouldn't have (e.g., the truck can change the state of terrain
directly by modifying attributes).  The alternative, it seems to me,
is to give the truck a map attribute.  That is, a reference to a map
object through which it can deal with terrain appropriately (via the
maps "public" interface and not "under the table" so to speack).

I guess the point I'm trying to make here is that composition via
multiple inheritance may give the composite object information it
doesn't need or shouldn't have access to (if there isn't a natural
"is-a" type relationship).  Composition by encapsulation (which may
not be an appropriate term since I just made it up) is a way around
this.

This "inheritance"/"composition" issue is one we've been trying to get
a grasp on too.  It seems that the line (for us anyway) is somewhat
subjective.  What's "is-a" to one person may not be to another.  In a
lot of ways, this may just be another "soap box" issue.

Dave
--




--
David Rich
Military Systems Analysis Group (A-5)
MS F602
Los Alamos National Laboratory
Los Alamos, NM 87545

Phone: (505) 665-0726
Email: dor@lanl.gov

davidm@uunet.UU.NET (David S. Masterson) (02/13/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
   In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>, [I] wrote:
   > I'm curious as to the reasons that one might use inheritance (derivation
   > of a new object from the definition of an old object) and the reasons
   > that one might use composition (wrapping multiple objects in another
   > object).
   >
   The key difference in C++ is that if class D inherits [publicly] from
   class B, then you can pass a D argument to any function that takes
   a B parameter.  Another way of looking at it is that if you derive
   D from B, then a D object does [at least] everything a B object does.
   If you define a class D with a B member, then you must define every
   operation you want a D to be able to perform, even if that is replicating
   many of the B operations.

This is interesting...

The tone here suggests that one idea is good and the other is not so good
(intentional?).  Also, except for "derive...[when] object does everything [the
base] object does", there's no real suggestion of a model to follow here.  Its
more of rules of thumb about programming style.  This is one of the points
that I seem to be finding in the use of C++ (and perhaps generally in OOP) by
programmers with a light background in the concepts (I'm certainly NOT
suggesting that Andrew Koenig has a light background in the concepts).
Instead of a design model to follow, the approach seems to be more of "do what
seems right programmatically" and the design will take care of itself.  Is
this appropriate or do people see the need for more "principles" to follow to
promote properly designed, reusable code development?

--
===================================================================
David Masterson					Consilium, Inc.
uunet!cimshop!davidm				Mt. View, CA  94043
===================================================================
"If someone thinks they know what I said, then I didn't say it!"

ark@alice.UUCP (Andrew Koenig) (02/13/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET>, cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
> In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:

>    The key difference in C++ is that if class D inherits [publicly] from
>    class B, then you can pass a D argument to any function that takes
>    a B parameter.  Another way of looking at it is that if you derive
>    D from B, then a D object does [at least] everything a B object does.

> This is interesting...

> The tone here suggests that one idea is good and the other is not so good
> (intentional?).  Also, except for "derive...[when] object does everything [the
> base] object does", there's no real suggestion of a model to follow here.

I do not intend to imply that one alternative is good and the
other bad -- I'm just trying to point out the differences.

Each approach has its place.  It's a little like trying to decide
whether to use screws or nails -- each can sometimes substitute for
the other, but each has advantages and disadvantages.
Which one you choose depends on the details of the problem
you're trying to solve.
-- 
				--Andrew Koenig
				  ark@europa.att.com

dag@control.lth.se (Dag Bruck) (02/13/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>   The key difference in C++ is that if class D inherits [publicly] from
>   class B, then you can pass a D argument to any function that takes
>   a B parameter.
>
>Instead of a design model to follow, the approach seems to be more of "do what
>seems right programmatically" and the design will take care of itself.  Is
>this appropriate or do people see the need for more "principles" to follow to
>promote properly designed, reusable code development?

In general, I think insight is more important than principles.

We often talk about reuse, but we must distinguish between (at least)
two different forms of reuse: reuse of code and reuse of abstraction.
The "American" argument for OOP is to reuse existing code, while the
"European" argument is to use inheritance as a structuring and
abstraction mechanism.  I find both equally important, but reuse of
code is much easier than reuse of abstraction.

I also believe reuse of code naturally lends itself to bottom-up
development, while reuse of abstraction lends itself to top-down
development. In practice we often alternate between the top-down and
the bottom-up approaches.

The C++ world is almost singularly concerned with code reuse.  This is
demonstrated whenever C++ is described, and also when Andrew Koenig
uses a function call for describing inheritance.  It is also obvious
that many Smalltalk, Objective-C and Eiffel friends believe that reuse
of abstraction is next to impossible in C++ -- a complete
misconception in my view.

Dag M. Bruck

gza@mentor.cc.purdue.edu (William R Burdick) (02/13/90)

The difference between inheritance (or generalization) and composition
(or aggregation)  is a tough point   to  figure  out without some good
examples (I hope I  can give some here).   Depending on  what language
you use and what you use your objects for, you may not be able to tell
what  the  difference is  right  off.   Mainly, inheritance  passes on
behavior to subclasses and composition passes on form to aggregations.

As  an  easy way to   tell the difference right  off   (using multiple
inheritance), consider readable  files, writable files, and read-write
files.  You  can say  that a  read-write file  inherits  behavior from
readable files  and writable files.   If you   were to   try to make a
read-write file out of a composition, you might end up  with an object
containing  two files, a readable file  and a  writable  file.  If you
make a Read-Write File (the class) a  subclass of Read File  and Write
File, you get a kind  of file which  can be read from and  written to.
classes use  inheritance   and  classes are   definitions  of objects.
Instances   use composition, and    instances  are   the  objects.   A
particular  read-write  file does   not inherit   behavior  from   its
superclass because it is not a class, it is a file, while a read-write
file could be composed of a read file and a write file.

When you want to  model objects which are  made of other  objects, use
composition, when you want  to  model objects  which behave like other
kinds of objects, use inheritance.  The key words  here are 'made of,'
'other objects,' 'behave  like,' and 'kinds of objects.'   When I  use
'kinds of objects,' I am referring to objects of other classes, when I
use 'other  objects,'  I am referring to just  any  other objects.  An
object  composed  of  other   objects shares     a   'made-of/part-of'
relationship with those objects, while  a class shares  a 'has-a/is-a'
relationship with its subclasses.

In OOPLs, you can use instance variables  for the part-of relationship
and you can use subclassing for the is-a relationship.

	
--
	-- Bill Burdick
	burdick@cello.ecn.purdue.edu

pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi) (02/13/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:

   In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET>,
   cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:

   > I'm curious as to the reasons that one might use inheritance (derivation of a
   > new object from the definition of an old object) and the reasons that one
   > might use composition (wrapping multiple objects in another object).

   The key difference in C++ is that if class D inherits [publicly] from
   class B, then you can pass a D argument to any function that takes
   a B parameter.  Another way of looking at it is that if you derive
   D from B, then a D object does [at least] everything a B object does.
   If you define a class D with a B member, then you must define every
   operation you want a D to be able to perform, even if that is replicating
   many of the B operations.

But this is just syntactic sugaring. And it leads to a lot of problems
as well, like all the funny rules about multiple inheritance, and the
ambiguity between inheritance as is-a and as part-of, and so on.

The inheritance issue is bogus and clouds the real problem, that
in sufficiently sophisticated applications you have a data
modeling and design problem of the same size and complexity as
that of a data base, and you need to apply the same conceptual
tools, and map the concepts onto ultimately contiguity or
pointers (the only ways we have in a computer to indicate a
relationship between data).

Just think that inheritance could be entirely obviated if one were
allowed to use composition with nameless members, or if a member could
be identified by any unambiguous path to it, like in PL/1.

Example:

	class vehicle { ... int weight, speed, cost; ... }	v0;
	class car : vehicle { ... int seats; ... }		c0;
	class truck : vehicle { ... int load; ... }		t0;

vs. (assuming you have unnamed members):

	class vehicle { ... int weight, speed, cost; ... }	v1;
	class car { ... vehicle; int seats; ... }		c1;
	class truck { ... vehicle; int load; ... }		t1;

or (assuming memberscan be named by any unambiguous way):

	class vehicle { ... int weight, speed, cost; ... }	v2;
	class car { ... vehicle v; int seats; ... }		c2;
	class truck { ... vehicle v; int load; ... }		t2;

You can write 'c0.speed' or 'c1.weight' or 't2.cost'
indifferently; the same goes of course for function members
(which should be abolished as well, by the way, by allowing any
function whose first argument is of a class type to be invoked in
infix position).

Getting rid of inheritance, and using only composition with some
sugaring to make members of members visible at the outer level, is
entirely possible.

	You also need a few paraphernalia, like sensible casts
	between	(pointer to a member)/(to the enclosing structure)
	and viceversa, and sensible member pointers.

It is desirable on two grounds: that inheritance is redundant, and it
clouds serious discussions about the many problems of data modeling
using composition.

	I strongly believe that inheritance was invented *only* because
	in Simula 67 class objects could only be accessed via references,
	and 'prefixing' (as it was more correctly called) was needed as
	an efficient way of concantenating object representations. The
	essentially linear nature of prefixing is what causes all the
	conceptual and implementation problems with MI.

	Notice also that in some cases MI has actually to be implemented
	using references in C++. Doesn't this point to a fundamental
	problem with the concept? :-(.
--
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

jimad@microsoft.UUCP (JAMES ADCOCK) (02/14/90)

In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>The key difference in C++ is that if class D inherits [publicly] from
>class B, then you can pass a D argument to any function that takes
>a B parameter.  Another way of looking at it is that if you derive
>D from B, then a D object does [at least] everything a B object does.
>If you define a class D with a B member, then you must define every
>operation you want a D to be able to perform, even if that is replicating
>many of the B operations.

More particularly, if you want to be able to use D as a B--and then some,
one must either/or inherit from B and make non-virtual methods of B available
unchanged from D, and/or inherit from B and possibly change some of the
virtual methods of B to reflect the added functionality of D.  In either 
case, the public methods of B can be thought of as a protocol that D
must conform to.  

The important thing to note is that in C++, unlike some other OOPLs, 
even if one delared the same-named methods as B in a class D' *not*
derived from B, those same-named methods *would-not* match B's protocol.
So D' could not be used as a B, even though D' *seemed* to have all the
correctly-named methods necessary to act like a B.  

D can be used as a B only via inheritence.

marc@dumbcat.UUCP (Marco S Hyman) (02/14/90)

In article <CIMSHOP!DAVIDM.90Feb12100359@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
    Instead of a design model to follow, the approach seems to be more of
    "do what seems right programmatically" and the design will take care
    of itself.  Is this appropriate or do people see the need for more
    "principles" to follow to promote properly designed, reusable code
    development?

You start out trying to follow the right principles but end up satisfying
the customer.  That is, when the ultimate goal is to ``finish the damn thing
and make it work'' you tend to do ``what seems right programmatically.''
How good or bad this is in the long run depends upon the language being used
and the skill level of the programmer.  The more highly skilled the
programmer, the more s/he can do with the language and the more that
``seems right.''  If the language supports the principles you wish to follow
the easier it is to ``do it right.''

Or am I the only one that feels frustrated when real world constraints force
me to program in C or Assembler after I've been having so much fun with
Smalltalk or C++?

// marc				{ames,pyramid,sun}!pacbell!dumbcat!marc

djones@megatest.UUCP (Dave Jones) (02/15/90)

From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>, by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
...
> 
> The inheritance issue is bogus and clouds the real problem ...

Hear! Hear! I've been wanting to say this, but I've been reluctant
to get into the scrap with people who know all the fashionalble buzz-words.

> ... [ to ] map the concepts ultimately onto contiguity or
> pointers (the only ways we have in a computer to indicate a
> relationship between data).
> 

That's what this all comes down to isn't it? That and what kind of
shorthand-resolution you want the compiler to do for you. (Not easy
questions, actually.)

When I get into a discussion of this stuff, and inevitably somebody
says something like, 'But that can all be solved with post-recurrent
semideclined prevaricated polytrophism,' I say, 'Mumble, ... refresh
my memory on what that means. What do the compiler's lookup-tables do,
and when do you use pointers at runtime?'

> Just think that inheritance could be entirely obviated if one were
> allowed to use composition with nameless members, or if a member could
> be identified by any unambiguous path to it, like in PL/1.
> 

Did PL/1 do that? It's been so long since I used that language -- over
fifteen years I guess -- I've forgotten most everything about it. PL/1
has been ragged so hard over the years, it seems strange to hear one
of its features mentioned in a positive way.

For the last few years, just for the fun of it, I've been dreaming up my
own sequential imperative language. Don't know if I'll ever get around to
implementing it. I had decided that all this 'inheritance' and 'composition'
debate comes down to nothing more than when to use pointers and what kind of
shorthand you prefer.  I decided on a scheme similar to the ones you describe.
Ditto with function-members. It's good to know that at least one other person
on this planet sees things the way I do.

adiseker@potomac.ads.com (Andrew Diseker) (02/15/90)

In article <CIMSHOP!DAVIDM.90Feb11005056@uunet.UU.NET> cimshop!davidm@uunet.UU.NET (David S. Masterson) writes:
>I'm curious as to the reasons that one might use inheritance (derivation of a
>new object from the definition of an old object) and the reasons that one
>might use composition (wrapping multiple objects in another object).  I have
>been getting definitions around my work area that tend to conflict with my own
>views and I'd like to build a body of evidence one way or the other in order
>to help make my own views understood.  Anyone care to provide their
>definitions with examples as to how to apply their definitions?
>
>--
>===================================================================
>David Masterson					Consilium, Inc.
>uunet!cimshop!davidm				Mt. View, CA  94043
>===================================================================
>"If someone thinks they know what I said, then I didn't say it!"


	What follows are my opinions only, flames will be gleefully ignored
(I've been roasted by the best %^).

	From my experience working with and having discussions with some
excellent OO programmers, I think the answer to the first question about
derivation could be this:  If there is an existing definition of an object
that is close ( however you define that ) to what you want to use, then
use your language's inheritance mechanism to create a copy of the definition
and modify the parts that don't do quite what you want, and add whatever 
you need for any extra functionality.  There, I didn't use the dreaded
'c' word, didn't mention data hiding, or any other religious terms.  I tried
to keep it general enough that it could even apply to (gag, choke) ADA.

	Now, however, it's time to don the asbestos suit.  The second part
of your question, referring to composition classes, is a matter of strong
opinions on two sides.  My personal reason for using composition objects
is to circumvent the need for multiple inheritance.  If I have two or
more classes which exhibit the functionallity I need, and which contain the
data in which I'm interested, then one object which contains instances of 
those classes and which allows whatever access is needed to those instances, 
is all I need.  I just haven't seen any schemes for MH ( acronyms, anyone? )
that are general enough to be effective, and specific enough to cover all
the holes.  If you want to know what I'm talking about, just watch the next
hundred or so responses to this post! %^) %^) %^)  I really hate to have
my compiler ( environment, whatever ) bogged down trying to prevent or 
otherwise handle the abiguities that naturally arise when you try to munge
multiple, possibly disparate, possibly related classes.  I call it a "what
happens when cousins marry?" problem.  Ahem.  Now that I've offended any
number of people, I'll step aside and watch the mayhem.  Hope I've helped
in some way, though.


-- 
Andrew Diseker                            ( )-( )   Mouse-Tested!
Advanced Decision Systems                  /o o\    Hacker-Approved!
UUCP: sun!sundc!potomac!adiseker          / =v= \ ,--_
Internet:  adiseker@potomac.ads.com       ;;---;;     `--'

sjs@spectral.ctt.bellcore.com (Stan Switzer) (02/16/90)

Dave Jones (djones@megatest.UUCP) writes:
> From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
>    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
> 
> > ... [ to ] map the concepts ultimately onto contiguity or
> > pointers (the only ways we have in a computer to indicate a
> > relationship between data).
> 
> That's what this all comes down to isn't it? That and what kind of
> shorthand-resolution you want the compiler to do for you. (Not easy
> questions, actually.)

I was meaning to comment on this earlier, but I let it pass.  There
are many ways to represent relationships in a computer.  For instance,
relational databases (conceptually) use an associative technique based
on the equality of attribute values.  Another associative technique is
pattern matching (grep, and X resource databases).  Other types of
reference relationships might include inter-addressing-environment
references, file references, and persistent-object store references.

Even granting the importance of contiguous values and pointer
references, there are all sorts of possible pointer semantics: "Atom"
semantics (eq <=> equal -- SETL, or some dialect of SETL, did this),
capability references, self-describing references, context-described
references, and simple storage-indexing memory references.

> When I get into a discussion of this stuff, and inevitably somebody
> says something like, 'But that can all be solved with post-recurrent
> semideclined prevaricated polytrophism,' I say, 'Mumble, ... refresh
> my memory on what that means. What do the compiler's lookup-tables do,
> and when do you use pointers at runtime?'

Sure, there is a lot of terminology out there, some of it needlessly
complicated, but this terminology _does_ attempt to make useful
distinctions.

> For the last few years, just for the fun of it, I've been dreaming up my
> own sequential imperative language.

What!  Another one?

Stan Switzer  sjs@bellcore.com

jss@jra.ardent.com (02/16/90)

In article <8361@potomac.ads.com> adiseker@potomac.ads.com (Andrew Diseker) writes:
>
>	From my experience working with and having discussions with some
>excellent OO programmers, I think the answer to the first question about
>derivation could be this:  If there is an existing definition of an object
>that is close ( however you define that ) to what you want to use, then
>use your language's inheritance mechanism to create a copy of the definition
>and modify the parts that don't do quite what you want, and add whatever 
>you need for any extra functionality.  

This may be good advice for "generic OO" but it is bad 
advice for C++.   

I believe that the "protected interface of a class" (i.e.  the 
relation between a class and classes derived from it) must be specified 
just as carefully as the specification of any member, and if 
the original class was not designed with such an interface then you 
shouldn't be deriving from it.  

Many C++ classes are designed with a value semantics rather than
an object semantics.  That is, they are intended to have their
values copied rather than pointers to them passed around.  The
classsical example is "complex".  Even when it isn't obvious
from the external interface, the implementation of a class
may copy values around in various ways.  It is almost always
a mistake to derive from these classes.

When I to use a class supplied by somebody else, I use it in 
the way it was designed to be used.  When I want to copy code,
I copy it.

Jerry Schwarz

px@fctunl.rccn.pt (Joaquim Baptista (pxQuim)) (02/16/90)

In article <GZA.90Feb13025537@mentor.cc.purdue.edu> gza@mentor.cc.purdue.edu (William R Burdick) writes:

   The difference between inheritance (or generalization) and composition
   (or aggregation)  is a tough point   to  figure  out without some good
   examples (I hope I  can give some here).   Depending on  what language
   you use and what you use your objects for, you may not be able to tell
   what  the  difference is  right  off.   Mainly, inheritance  passes on
   behavior to subclasses and composition passes on form to aggregations.

   As  an  easy way to   tell the difference right  off   (using multiple
   inheritance), consider readable  files, writable files, and read-write
   files.  You  can say  that a  read-write file  inherits  behavior from
   readable files  and writable files.   If you   were to   try to make a
   read-write file out of a composition, you might end up  with an object
   containing  two files, a readable file  and a  writable  file.  If you
   make a Read-Write File (the class) a  subclass of Read File  and Write
   File, you get a kind  of file which  can be read from and  written to.
   classes use  inheritance   and  classes are   definitions  of objects.
   Instances   use composition, and    instances  are   the  objects.   A
   particular  read-write  file does   not inherit   behavior  from   its
   superclass because it is not a class, it is a file, while a read-write
   file could be composed of a read file and a write file.


In article <GZA.90Feb13025537@mentor.cc.purdue.edu> gza@mentor.cc.purdue.edu (William R Burdick) writes:

   We often talk about reuse, but we must distinguish between (at least)
   two different forms of reuse: reuse of code and reuse of abstraction.
   The "American" argument for OOP is to reuse existing code, while the
   "European" argument is to use inheritance as a structuring and
   abstraction mechanism.  I find both equally important, but reuse of
   code is much easier than reuse of abstraction.


While I was reading this I could not stop myself from wandering about
delegation, one of the mechanisms proposed in the Actor model. I could
profit from some cleaning up of the "name space" that includes
Inheritance, Composition and Delegation.

*** Inheritance

Whatever inheritance is used for, I guess we all agree on what it is.
It is a mechanism by which you say that a "thing" (object, class, or
whatever) is made from another "thing", by changing some functionality
(or declarativity, or whatever). This is, of course, a form of "reuse
of code" or "reuse of abstraction", whatever the programmer or
designer has in mind.

*** Composition

Composition, on the other hand, seems to have been described as
building a "thing" which uses other "things". In a Smalltalk way, It
would be like having an object with two other objects as instance
variables.

I think this last definition should be broken in two different things.
On the first case, the functionality (or interface, or whatever) or the
compose "thing" is very similar to the functionality of the "things" it
is made of. A good example would be the relation between a read-write
"thing" and the corresponding read and write "things". This is a case
where I think that some kind of multiple inheritance is appropriate.

Multiple inheritance with some kind of "merging" of common instance
variables and some kind of selecting or overwriting of the common
methods would probably do the trick.


The other case of composition arises when the functionality of the new
object is quite different from the functionality of its components.
For instance, suppose I have some read "thing" that allows me to read
characters, and that I want to build another one that reads tokens.
The token reader "thing" will probably "own" a character reader
"thing", but I find it difficult to mantain that the new object is
"composed" from the previous one.

In this case, I think I am just reusing abstractions, ie, I built an
abstract token reader from an abstract character reader.

So, either I am missing something, or the word "composition" seems
empty of content to me. But...

*** Delegation

The word "delegation" means to me the following: when asked to do
something, an object will instead ask another object to do the same
thing. The Actor model seems to use this concept to model
continuations (I'm not sure though, I was never able neither had the
need to fully understand their model).

So, we could think that delegation is a word to be used by the Actor
model alone. But my concept of delegation has been very helpful in
Contextual Logic Programming (aka CxLP; see reference). CxLP just
provides a basic notion of unit (sort of model with global parameters)
and it is very useful to delegate some of the tasks of a unit to
another one. This is often the case when you are programming a layered
structure.

*** Proposal

So, to get this posting to an end, I propose the following "standard
cases of object programming":

* Single Inheritance:	to be used when one "thing" can be described by
			adding some (usually small) changes to an
			other one.

* Abstraction Reuse:	to be used when some "thing" just needs the
			functionality of another one to get some work
			done.

* Delegation:		a special case of Abstraction reuse in which
			part of the functionality of a "thing" is just
			the functionality of another "thing".

I did left Multiple Inheritance out, for I think it has problems when
one tries to inherit the same functionality from several "things" at
the same time. The problem is NOT that it can't be solved, but rather
that each person will want it solved in the way that best fits the
problem they have at hand. I don't like the "throw everything in"
solution, so I just left it out.

I also left the Code Reuse thing out. One can always use inheritance
for Code Reuse instead of Abstraction Reuse, although you will
probably end up with some very strange designs. One can also define
some "thing" called Reusable_Code_Not_Otherwise_Related... and reuse
it.


*** References

This is the standard paper on Contextual Logic Programming.

%A Luis Monteiro
%A Antonio Porto
%P 284-299
%B Logic Programming: Proc. of the 6th International Conference
%S MIT Press series in logic programming
%E Giorgio Levi and Maurizio Martelli
%C Lisboa
%D 19-23 June 1989
%G ISBN 0-262-62065-0
%K =CxLP
%X The notion of contextual logic programming is proposed as an extension to
the logic programming paradigm. It is tied to the development of a
structuring theory for logic programming in which modules, called here
units, are sets of context-dependent predicate definitions, and the proof
of a certain kind of formulae, called extension form

sakkinen@tukki.jyu.fi (Markku Sakkinen) (02/16/90)

In article <PCG.90Feb13153607@rupert.cs.aber.ac.uk> pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi) writes:
>In article <10465@alice.UUCP> ark@alice.UUCP (Andrew Koenig) writes:
>> ...
>But this is just syntactic sugaring. And it leads to a lot of problems
>as well, like all the funny rules about multiple inheritance, and the
>ambiguity between inheritance as is-a and as part-of, and so on.

There is sense behind this oversimplification. However, you could go all
the way and say that any programming language is just a universal Turing
machine with some syntactic sugaring; it tastes better to me with at least
a little sugar.

>The inheritance issue is bogus and clouds the real problem, that
>in sufficiently sophisticated applications you have a data
>modeling and design problem of the same size and complexity as
>that of a data base, and you need to apply the same conceptual
>tools, and map the concepts onto ultimately contiguity or
>pointers (the only ways we have in a computer to indicate a
>relationship between data).

That we have a limited choice in physical implementation has no
bearing on what conceptual tools and models we can or should use.
(Besides, there are other ways, e.g. "foreign keys".)
Many people in the data base community are applying
OO concepts, including inheritance. It is one of the mandatory
features suggested in "The object-oriented database system manifesto",
which was written by Atkinson, Bancilhon, DeWitt, Dittrich, Maier,
and Zdonik for the DOOD'89 conference.

>Just think that inheritance could be entirely obviated if one were
>allowed to use composition with nameless members, or if a member could
>be identified by any unambiguous path to it, like in PL/1.
> ...
>Getting rid of inheritance, and using only composition with some
>sugaring to make members of members visible at the outer level, is
>entirely possible.

This is pretty much in line with what both Raj and Levy - "A compositional
model for software reuse" and myself - "Disciplined inheritance"
presented at ECOOP'89. I think both parties had at least played with
the idea of calling the paper - you guessed it - "Inheritance
considered harmful".

> ...
>	I strongly believe that inheritance was invented *only* because
>	in Simula 67 class objects could only be accessed via references,
>	and 'prefixing' (as it was more correctly called) was needed as
>	an efficient way of concantenating object representations. The
>	essentially linear nature of prefixing is what causes all the
>	conceptual and implementation problems with MI.

I agree with much of this diagnosis, although I doubt '*only*'.
Incidentally, even two very new languages, Modula-3 and Oberon
(the latter extremely Spartan even in other ways),
seem to follow the same prefixing principle and stick
confidently to single inheritance. By the way, it still holds for almost
all common OOPL's that class objects can only be accessed via references.
- All conceptual problems of MI don't stem from the linear nature of
prefixing as far as I can see: e.g. the Flavors model looks bad to me
because it doesn't preserve the integrity of the superclass parts.

>	Notice also that in some cases MI has actually to be implemented
>	using references in C++. Doesn't this point to a fundamental
>	problem with the concept? :-(.

No, in my analysis (see the ECOOP paper) the MI _principles_ of C++ are
sounder than any others I have studied. The _implementation_ of MI has
evidently become cumbersome because C++ has offered the strong
concept of object identity (as seen in e.g. Smalltalk) on the altar
of "efficiency".

Markku Sakkinen
Department of Computer Science
University of Jyvaskyla (a's with umlauts)
Seminaarinkatu 15
SF-40100 Jyvaskyla (umlauts again)
Finland
          SAKKINEN@FINJYU.bitnet (alternative network address)

markv@kuhub.cc.ukans.edu (02/17/90)

All philosophy and religion aside (I know, SACRILEGE...) heres my
thoughts on Inheritance -vs- composition.  I am working one my first
"serious" C++ project (personal interest).  It is a program that uses
2D structured graphics which seemed like a natural.

I kind of like the idea that programming reality makes a lot of the
decisions.  Kind of a form follows function argument.  I am building this
program around and object called Object.  An Object is defined (via
virtual functions) to have a common interface so it can Show() itself,
Erase(), do various geometric transformations (like FlipV() and
FlipH()).  All of these Objects are stored in a linked list, so an
Object also can Insert(), Delete(), etc itself too.

Then Object is derived into things like Line, Circle, Box, etc.  Now,
to make my life easy I need to be able to pass pointers to an Object
all over, and I don't know until runtime what kind of Object it will
be so Inheritance (and virtual functions) are the only solution.

On the other hand I also have objects like a Point and a LineSeg etc
that are used to build the specific representation of an Object.  
I know which of these are going into an Object (like a FreePolygon)
at compile time.

So one (simplisitic but pratical argument) is that when you need to
have common "methods" that must be resolved at run-time (ie: Late
binding) you must (in C++) use Inheritance.  On the otherhand when you
know at compile time what you need to be dealing with, composition
works.

Being new to C+ (and OOP) this may be a simplistic explanation, but it
is a rationale that works for me.  This whole argument first took
shape back (a month ago) when I first started working with C++ and I
got confused with the difference between overloaded functions, and
vitual functions.

Well, have fun,

-- 
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mark Gooderum			Only...		\    Good Cheer !!!
Academic Computing Services	       ///	  \___________________________
University of Kansas		     ///  /|         __    _
Bix:	  markgood	      \\\  ///  /__| |\/| | | _   /_\  makes it
Bitnet:   MARKV@UKANVAX		\/\/  /    | |  | | |__| /   \ possible...
Internet: mark@kuhub.cc.ukans.edu
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

djones@megatest.UUCP (Dave Jones) (02/17/90)

From article <20020@bellcore.bellcore.com>, by sjs@spectral.ctt.bellcore.com (Stan Switzer):
...
> 
>> For the last few years, just for the fun of it, I've been dreaming up my
>> own sequential imperative language.
> 
> What!  Another one?
> 

Why not? I said, 'just for the fun of it.' Besides, I'm not satisfied with
any that I've seen so far.

djones@megatest.UUCP (Dave Jones) (02/17/90)

From article <20020@bellcore.bellcore.com>, by sjs@spectral.ctt.bellcore.com (Stan Switzer):
> Dave Jones (djones@megatest.UUCP) writes:
>> From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
>>    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
>> 
>> > ... [ to ] map the concepts ultimately onto contiguity or
>> > pointers (the only ways we have in a computer to indicate a
>> > relationship between data).
>> 
>> That's what this all comes down to isn't it? That and what kind of
>> shorthand-resolution you want the compiler to do for you. (Not easy
>> questions, actually.)
> 
> I was meaning to comment on this earlier, but I let it pass.  There
> are many ways to represent relationships in a computer.  For instance,
> relational databases (conceptually) use an associative technique based
> on the equality of attribute values.  [...]

These other relationship techniques you named are all search-and-test
operations. You still need some way to find that something to test. Notice
that Mr. Switzer used the word 'ultimately'. These search-and-test
operations are indeed ways to indicate relationships, but they *ultimately*
depend on contiguity or pointers. Contiguity and pointers are the
atomic operations of association in digital computers.

Still the question remains as to whether you want the higher level
operations to be an integral part of a programing language, as
hash-tables are, for example, in AWK, or do you want to furnish
these capabilities as {utility/library/class} functions which extend
a basic language?  My current opinion is that if the language has good
enough extention mechanisms, it's best to start with only a small set
of primitive operations, and build. That way, you don't tend to get
'languaged into a corner' with this language having this feature and that
language having that one, and no way to mix them. (I often wish I could link
my C-procedures into an AWK program.) Problem is, I don't know of any
language whose extention mechanisms are 'good enough'. But then,
I really haven't been keeping up over the last few years. C++ is close,
though.

pcg@aber-cs.UUCP (Piercarlo Grandi) (02/19/90)

In article <20020@bellcore.bellcore.com> sjs@bellcore.com Switzer) writes:
    Dave Jones (djones@megatest.UUCP) writes:
    > From article <PCG.90Feb13153607@rupert.cs.aber.ac.uk>,
    >    by pcg@rupert.cs.aber.ac.uk (Piercarlo Grandi):
    > 
    > > ... [ to ] map the concepts ultimately onto contiguity or
    > > pointers (the only ways we have in a computer to indicate a
    > > relationship between data).
    > 
    > That's what this all comes down to isn't it? That and what kind of
    > shorthand-resolution you want the compiler to do for you. (Not easy
    > questions, actually.)
    
    I was meaning to comment on this earlier, but I let it pass.  There
    are many ways to represent relationships in a computer.

I beg to differ. In a *computer* you have memory as a block of storage
units, and composite entities can only be built out of contiguity or
pointers. C++ is a language that tries hard to let you do *implementation*
level work, that is "full unrestricted" access to the underlying hardware.

The type constructors in C++, and other machine oriented HLLs, are records
and arrays for heterogenous and homogeneous contiguity, and pointers.

    For instance, relational databases (conceptually) use an associative
    technique based on the equality of attribute values.  Another
    associative technique is pattern matching (grep, and X resource
    databases).  Other types of reference relationships might include
    inter-addressing-environment references, file references, and
    persistent-object store references.

But these are all *program* level abstractions. One of the big tasks of
programming is to map all the useful data structuring abstractions you have
(and these are usually content based) onto contiguity and pointers. An
especially hard job because contiguity is bad for dynamic resizing and
pointers by their very nature are one way only, and this has many profound
consequences (e.g. on garbage collection) in all the common cases where you
have many-to-many abstractions to represent (just think that essentially
network databases are there only to give you bidirectional pointers).

It is notable about C++ is that BS has carefully designed it to retain by
and large (the two notable exceptions being constructors/destructors and
multiple virtual inheritance) the MOHLL flavour of C. Eiffel for example,
not to speak of CLOS or Simula 67, do not have this aim.

What I find disagreeable about virtual multiple inheritance (I also don't
like much constructors destructors, incidentally) is that what you think
is an object is actually implemented as a graph (tree) of subobjects. This
may well be too high level. It is my impression that thsi is because of
prefixing.
-- 
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

pcg@aber-cs.UUCP (Piercarlo Grandi) (02/19/90)

In article <12029@goofy.megatest.UUCP> djones@megatest.UUCP (Dave Jones) writes:
    > allowed to use composition with nameless members, or if a member could
    > be identified by any unambiguous path to it, like in PL/1.
    
    Did PL/1 do that? It's been so long since I used that language -- over
    fifteen years I guess -- I've forgotten most everything about it. PL/1
    has been ragged so hard over the years, it seems strange to hear one
    of its features mentioned in a positive way.

I seem to remember that also Cobol allows you to name a member with the
shortest unique path to it. This feature may be seen as dangerous, as it
means that modifying a data structure can make a program ambiguous, but
this is really unavoidable, with inheritance as well.

	I had written in my C++ coding guidelines to prefix all identifiers
	used in a function with their scope operators. Somebody observed
	that this also should extend to members (prefixing them from the
	name of the class from which they are inherited), obviously not just
	to improve readability (where is this member from?), but also
	to guard against hazardous ambiguities caused by extending member
	lists in the future.

As to PL/1, it actually had some *good* ideas, and some ridiculous ones
(exceptions as a datatype!, 22/7 resulting in overflow,...). For example
areas, based pointers, controlled storage. Not that I think they were all
appropriate at the language level; for example, controlled storage is done
in GNU LIB C++ as the obstack class, and areas can be done in C++ 2.0 with
operator new overloading. On the other hand I would really be happy to see
based (relative) pointer support, which is vital to get position independent
data structures (and no, "smart pointers" are not a full solution).
-- 
Piercarlo "Peter" Grandi           | ARPA: pcg%cs.aber.ac.uk@nsfnet-relay.ac.uk
Dept of CS, UCW Aberystwyth        | UUCP: ...!mcvax!ukc!aber-cs!pcg
Penglais, Aberystwyth SY23 3BZ, UK | INET: pcg@cs.aber.ac.uk

peterli@ucsfmis.ucsf.edu (Peter Li) (02/21/90)

In article <GZA.90Feb13025537@mentor.cc.purdue.edu> gza@mentor.cc.purdue.edu (William R Burdick) writes:
>The difference between inheritance (or generalization) and composition
>(or aggregation)  is a tough point   to  figure  out without some good
>examples (I hope I  can give some here).
> ...[deleted]
>In OOPLs, you can use instance variables  for the part-of relationship
>and you can use subclassing for the is-a relationship.

Here is a monkey-wrench:

Consider the classes INTEGER and RATIONAL, 
would you say RATIONAL inherit from INTEGER,
or RATIONAL is completely independent but
composed of two INTEGERs, or ...
INTEGER is a subset of RATIONAL (something
that OOP doesn't address) ?

You can extend this into REALs and COMPLEXes.

Figuring out mathematical semantics vs.
representations is sure messy ... :-).

Peter Li

Clem.Clarke@csource.oz.au (Clem Clarke) (02/22/90)

     In article <12029@goofy.megatest.UUCP> djones@megatest.UUCP
     (Dave Jones) writes:     > allowed to use composition with
     nameless members, or if a member could     > be identified by
     any unambiguous path to it, like in PL/1.

 PG>     Did PL/1 do that? It's been so long since I used that
 PG> language -- over fifteen years I guess -- I've forgotten
 PG> most everything about it. PL/1 has been ragged so hard over
 PG> the years, it seems strange to hear one of its features
 PG> mentioned in a positive way.

IMHO PL/I is one of the most underated programming languages ever
written.  I have used it for application programs and system programming
since its very first version.  I have used on CPM and MSDOS as well.  It
seems to me that there is nothing that C can do that PL/I can't do, and
often in a very much nicer way.

I think the language design gives a more portable language than C in
many cases.  For example, you don't define INTs, LONG INTs etc, you
specify how long you want the integer in BITS which is very portable.
For example:

      dcl i bin fixed(15);
      dcl j bin fixed(31);


Recently, I started to convert some PL/I code to C, and just kept on
running into problems.  For example, BASED storage is SUPER, but can't
easily be done in C.

This program is called the Jol Universal Command Language, and runs on
quite a few machines now.  It provides a common command language for a
wide variety of machines, and soon UNIX too.

I have temporarily given up converting it to C.  I converted the code to
Turbo Pascal, which will do just about everything PL/I will (except bit
handling and a few other things).

There are a number of PASCAL to C translators that will help with the
conversion to C.  However, most UNIX systems have PASCAL, and so it
may stay in PASCAL for a while yet.

 PG> I seem to remember that also Cobol allows you to name a member
 PG> with the shortest unique path to it. This feature may be seen
 PG> as dangerous, as it means that modifying a data structure can
 PG> make a program ambiguous, but this is really unavoidable, with
 PG> inheritance as well.

When you get used to the idea, I don't think it dangerous.  It also
allows you to perhaps move a variable in or out of a structure, and
not change all your code.

 PG> As to PL/1, it actually had some *good* ideas, and some
 PG> ridiculous ones (exceptions as a datatype!, 22/7 resulting in
 PG> overflow,...).

I am sorry, I do not understand you comment about "exceptions as a
datatype".  It does have a statement like:

       ON ENDFILE(input)
       begin;
             eof=true;
             goto end_input;
       end;

This saves CPU time by not having to test EOF everytime you read
a record.

Also, I do not understand the statement 22/7 resulting in overflow.


 For example areas, based pointers, controlled
 PG> storage. Not that I think they were all appropriate at the
 PG> language level; for example, controlled storage is done in GNU
 PG> LIB C++ as the obstack class, and areas can be done in C++ 2.0
 PG> with operator new overloading. On the other hand I would really
 PG> be happy to see based (relative) pointer support, which is
 PG> vital to get position independent data structures (and no,
 PG> "smart pointers" are not a full solution).

Yes, PL/I has some very good ideas that I would LOVE to see put into C.
Based storage is definitely one of them.  It allows you to change a
structure from say an External one to one obtained with MALLOC/ALLOC and
not change any code.

PASCAL has WITH POINTER^ DO, but C doesn't have any equivalent.  It
seems that the nearest you can get is to define a name as something
like:

       #define based_var    p->based_var

And if you have a long structure, many names would have to be defined.

One of the biggest ommissions in C (compared with PL/I) is, IMHO,
strings.  Using C's method of string handling means that the compiler
(or functions) must search for the binary zero everytime strings are
moved or compared.  This is extremely expensive in machine time.


Incidently, I have developed some C macros that simulate some of PL/I's
string handling.  These macros run between 5 and 24 times faster than

    while (*dest++=*src++);

style of string copies.

Both Fixed and Varying length strings are supported (including blank
fill for Fixed Strings).  A generic CPY macro actually copies the
strings, taking into account whether the strings are C strings, PL/I or
PASCAL style strings.

For example:


    dcl (fixed100,charfixed,100,"Fixed",static);
/*
            ^        ^       ^     ^       ^
            |        |       |     |       |
   Variable |        |       |     |       |
    Name   >^        |       |     |       |< Storage Attribute
                     |       |     |
                     |       ^     |
    Specifies        |      max    | < Initial Value "Fixed"
    Fixed Length     |     length
    PL/I or Pascal   |
    String          >^

*/

    dcl (var100,charvar,100,"Varying",static);


    dcl (tempf3,charfixed,3,"333",static);
    dcl (tempf4,charfixed,4,"444",static);
    dcl (tempf5,charfixed,5,"5555",static);
    dcl (tempf6,charfixed,6,"666666",ext);
    dcl (tempv6,charvar,6,"666666",ext);

    int i;

main()
{

 /* Showing copies for Fixed Strings.

    Note: When copying long strings to short, truncation occurs.
          When copying short strings to long, blank fill occurs.
 */

    cpy(tempf3,tempf4);           /* Copy 3 from 4 bytes  */
    cpy(tempf4,tempf3);           /*      4      3        */
    cpy(tempf4,tempf5);           /*      4      5        */
    cpy(tempf6,tempf3);           /*      6      3        */
    cpy(tempf6,tempf5);           /*      6      5        */
    cpy(tempf6,tempf4);           /*      6      4        */

    cpy(tempf4,fixed100);         /*      4      100      */
    cpy(fixed100,tempf3);         /*      100 from 3      */

}

Cheers, Clem Clarke

----------------------------------------------------------------------
Clement V. Clarke                                  Tel (61)-3-822-3503
CCS-JOL Pty Ltd,                                   Fax (61)-3-882-9771
PO Box 475,
Toorak, AUSTRALIA, 3142
----------------------------------------------------------------------

--- via Silver Xpress V2.20
 * Origin: Micom CBCS - Australia's Longest Running Bulletin Board
--  
Via Fidonet/ACSNET Gateway, Melbourne, Australia
       UUCP: ...!munnari!csource!Clem.Clarke      FidoNet: 3:632/348
   Internet: Clem.Clarke@csource.oz.au