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