kmcentee@Apple.COM (Kevin McEntee) (05/13/89)
Meyer's Object Oriented Software Construction recommends that subclassing should take place only in the context of the is-a relationship. e.g. Every dog is a mammal. This seems to imply that inheritance is first a classification scheme and only second a code sharing mechanism. What are your thoughts on this? Can code sharing outside of the natural taxonomy of the problem space happen gracefully? Thanks, Kevin McEntee kmcentee@apple.com
bpe@brunix (Page Elmore) (05/14/89)
I do not think that inheritence is ever classification: it is specialization. This misconception will cause you lots of trouble when you attempt to define a class hierarchy. Classification implies something like: Type Car color:Red, Blue, Grey, Black, White, etc. | | | Type Honda CRX (inherits from Car) color: Red, Black Thus you attempt to constrain the value class of the color attribute, causing no end of trouble code which attempts to use a Honda CRX in place of a Car. Page Elmore
riks@csl.sony.JUNET (Rik Smoody) (05/15/89)
In article <30582@apple.Apple.COM> kmcentee@Apple.COM (Kevin McEntee) writes: >should take place only in the context of the is-a relationship. > > e.g. Every dog is a mammal. But not all dogs are frisbeeCatchers. Do I need a special sub-class? Most dogs who catch frisbees use their mouth. Do we need special code for each dog or class who is to learn that trick? How would the subclasses by abilities combine with the subclasses such as Collie, AustralianShepherd, Spotted, LongHaired, FinickyEater, MailManBiting, etc. Soon, I might have an inordinately large set of classes, with such taxonomic gems as Every BurglarBarkingFrisbeeFetchingBlackAndWhiteLongHairedOmnivorousAustralianShepherdDog is-a FrisbeeFetchingBlackAndWhiteLongHairedOmnivorousAustralianShepherdDog (only slightly more sightly if I don't make the artificial restriction to single-inheritance) > >This seems to imply that inheritance is first a classification scheme and >only second a code sharing mechanism. The hard part about code sharing is knowing when. That's true for objects which belong to classes, and for procedures which get called from different places with different arguments Fortran or C. There's a baby in that bath-water. Where does the "code" of tieShoeLaces get attached to the taxonomy of humans? (Oops... can't a monkey learn how to tie shoes? Two sparrows in a Disney cartoon?) Most of us do it the same way. A good candidate for code sharing. > >What are your thoughts on this? Can code sharing outside of the natural >taxonomy of the problem space happen gracefully? Don't blindly assume a natural taxonomy, even in nature. Many great minds have argued inconclusively on the natural taxonomy of biological systems. Since we in CS are not bound by such constraints as species development by evolution, it seems a long reach to assert that any one taxonomy is comprehensive. Taxonomizing (is that a word?) and Code Sharing are not equivalent. We all have a blood type. That's a taxonomy. Exposure to certain diseases is another taxonomy. Profession is another. People of almost any profession (not the oldest), who do not possess any of the forbidden risk factors, should all share the code and give blood now and then. "Most generalizations are pretty good" Rik Fischer Smoody Sony Computer Science Lab, Inc. 3-14-13 Higashigotanda, Shinagawa-ku Tokyo 141 Japan phone: (03)448-4380 E-mail: riks@csl.sony.jp or maybe: riks@csl.sony.co.jp -- Rik Fischer Smoody Sony Computer Science Lab, Inc., 3-14-13 Higashigotanda Shinagawa-ku, Tokyo 141 Japan (03)448-4380
kain@object.UUCP (Kai Ng) (05/16/89)
In article <30582@apple.Apple.COM> kmcentee@Apple.COM (Kevin McEntee) writes: > >Meyer's Object Oriented Software Construction recommends that subclassing >should take place only in the context of the is-a relationship. > > e.g. Every dog is a mammal. > >This seems to imply that inheritance is first a classification scheme and >only second a code sharing mechanism. > >What are your thoughts on this? Can code sharing outside of the natural >taxonomy of the problem space happen gracefully? > Dr. Meyer's "is-a" rule provides an easy to understand rule of thumb for subclassing. Since object oriented programming has only emerged, even in the academic world, for a relatively short period of time, programmers at large in the production world are admittedly not well prepared to take on such paradigm. That is the reason why the questions like "When should I subclass ?" "From which class I should inherit from ?" "Is it appropriate to add such a feature (method) to the heir class(es) ?" "Is it worthy to introduce a deferred (abstract) class in between ?" .... are heard many many times over. An important advantage of OOP, other than the familiar facilitating code re-use, is good understandability which is achieved via some OOP conventions and practices such as good naming, peronalizing (objectizing) objects and rules like "is-a", etc. Although it is arguable whether integrating common sense, like a dog is a mammal, with inheritance structure has too much restriction or not, it is not questionable with the aid of such kind of common sense, the characteristics of an object can be perceived better. It is not uncommon programmers do not trust each other's code; and the severity tends to be exponential inversely proportional to the degree they understand the code. Hence don't feel supprised if you find a Car object is more than 4-wheel drive. My point is understandability encourages code re-use. Removing the natural taxonomy inevitably, I believe, will remove the power of common sense. You can draw your own conclusion then. Even if you view inheritance as a classification scheme is not bad; most software libraries classify various modules in some manner anyway. Although I feel using the concept of generalization v.s. specialization is a more suitable rule for subclassing, I always attempt not to break the "is-a" rule. The "is-a" rule is almost perfect in any single inheritance environment, nevertheless it does has its short falls in a true mulitple inheritance environment like Eiffel. The case is worse on the hand of a good OO programmer who always manage to re-use more classes, i.e. inherit from more classes at once. The problem is self-explained in the following example. CLASS SmallInteger -- General purpose small integer. INHERIT MachineConstants; Number ... END -- Class SmallInteger The "is-a" rule is ok with Number-SmallInteger whereas it is not appliable at all for MachineConstants-SmallInteger. In our group, we introduce a keyword in the form of comment to clarify this. INHERIT -- Use -- MachineConstants; -- Inherit -- Number ... If the inheritance status is USE, no feature is allowed to be exported. It would be nice if USE is a real Eiffel keyword and exporting is enforced by the compiler itself. -- Kai Ng P.O. Box 9707 UUCP: uunet!mitel!sce!cognos!kain Cognos Incorporated 3755 Riverside Dr. (613) 738-1440 Ottawa, Ontario, ext. 6114 CANADA K1G 3Z4
dld@F.GP.CS.CMU.EDU (David Detlefs) (05/16/89)
Page Elmore writes -- >I do not think that inheritence is ever classification: it is specialization. >This misconception will cause you lots of trouble when you attempt to define >a class hierarchy. Classification implies something like: > > Type Car > color:Red, Blue, Grey, Black, White, etc. > | > | > | > Type Honda CRX (inherits from Car) > color: Red, Black >Thus you attempt to constrain the value class of the color attribute, causing >no end of trouble code which attempts to use a Honda CRX in place of a Car. If this is classification, what is it that you think of as specialization? Whatever you call it, I think this is exactly the kind of use of inheritance that Meyer and Liskov, among others recommend. I don't see where the fact that the set of colors that a Car may be is larger than the set of colors supported by HondaCRX. To make this work properly, you would only be allowed to specify the color of a Car at the time of creation. If you created a more specialized (or "classified" -- I really don't understand your terms) Car like a HondaCRX, it's creation function would constrain the set of acceptable colors. After creation, HondaCRX should fulfil Car's specifications of each of it's operations. I really think this works out fine, and is the right way to use inheritance. Meyer's book and Liskov's article in OOPSLA (87? 88?) make this point persuasively. -- Dave Detlefs Any correlation between my employer's opinion Carnegie-Mellon CS and my own is statistical rather than causal, dld@cs.cmu.edu except in those cases where I have helped to form my employer's opinion. (Null disclaimer.) --
plogan@mntgfx.mentor.com (Patrick Logan) (05/17/89)
In article <30582@apple.Apple.COM> kmcentee@Apple.COM (Kevin McEntee) asks:
=? Can code sharing outside of the natural taxonomy of the problem
=? space happen gracefully?
Here are some thoughts, written humbly as a would-be OO programmer not
as an expert sage. (Just in case it comes across that way.)
Perhaps in "Object Oriented Programming" there is no difference
between code sharing and classes sharing a common class. In languages
that are nearly "pure" OOLs (e.g. Smalltalk, Eiffel) it seems there is
no way to share code without specifying the sharing as a common super
class.
Is there a reason to share code some other way? In hybrid OOLs such as
CLOS and C++ it's possible to revert to creating good old procedures.
What would be a reasonable situation to do so? I usually think of
doing so in order to create a local procedure as a part of
implementing a particular method. Or to be shared between methods of a
particular class. If I was sharing a procedure in methods of disparate
classes I would probably look for a missing link in the class
taxonomy.
Also, there's an article* that suggests an "algorithm" should in some
cases be considered an object, e.g. FFTs. So one could create a class
for a particular procedure or group of procedures that may not usually
be considered objects. These would probably be few and far between.
* "Using Types and Inheritance in Object Oriented Programming",
Halbert, Daniel C. and Patrick O'Brien, (both at DEC)
IEEE Software, Sept. 1987
--
Patrick Logan | ...!{decwrl,sequent,tessi}!mntgfx!plogan
Mentor Graphics Corporation | plogan@pdx.MENTOR.COM
Beaverton, Oregon | "My other computer is a Lisp machine."
kentb@Apple.COM (Kent Beck) (05/17/89)
I think the confusion over this issues arises from the inheritance hierarchy being used for two purposes- classification sharing and code sharing. If I have only one hierarchy to work with (as in Smalltalk) I use it to share code ONLY, but I'd really rather have two hierarchies. Kent
dlw@valens.odi.com (Dan Weinreb) (05/17/89)
In article <10248@riks.csl.sony.JUNET> riks@csl.sony.JUNET (Rik Smoody) writes: In article <30582@apple.Apple.COM> kmcentee@Apple.COM (Kevin McEntee) writes: >should take place only in the context of the is-a relationship. > > e.g. Every dog is a mammal. But not all dogs are frisbeeCatchers. Do I need a special sub-class? In just about all of the popular object-oriented languages, an object is a direct member of exactly one class. So if you need special behavior for frisbee_catchers, then yes, you really do need a special class. Of course, there's no hope here without multiple inheritance, since ability to catch frisbees is orthagonal to biological order. The graph would look like this: mammal frisbee-catcher | | dog | | | \ / frisb-o-pup This is perfectly consistent with the original claim. A frisb-o-pup is a dog, a dog is a mammal, and a frisb-o-pup is a frisbee-catcher.
jwd@ihlpl.ATT.COM (Davison) (05/17/89)
In article <30582@apple.Apple.COM> kmcentee@Apple.COM (Kevin McEntee) writes: > >Meyer's Object Oriented Software Construction recommends that subclassing >should take place only in the context of the is-a relationship. > > e.g. Every dog is a mammal. > >This seems to imply that inheritance is first a classification scheme and >only second a code sharing mechanism. > >What are your thoughts on this? Can code sharing outside of the natural >taxonomy of the problem space happen gracefully? > ^^^^^^^^^^ I'm glad you added that last word. It seems to me there are several non-taxonomic reasons one might use inheritance for particular methods, but inheriting all methods from a class seems to imply some sort of strong relationship between the classes. If not "is-a" perhaps "acts-like-a" or "impersonates." I feel the inheritance structures ought to have an epistemological basis of some kind. It seems to me that the work done in knowlege representation systems is directly applicable to many questions like this. I recommend Ron Brachman's paper "I lied about the Trees", AI Magazine, Vol 6, No 3, Fall, 1985, for instance. Constantine's/Yourdon's ideas on coupling/cohesion in Structured Design (Prentice Hall, 1979) also seems relevant, as do database normalization ideas. Inheritance for individual methods is often desired because there is "one" thing that needs to be done, and it should be only specified once, but it really doesn't "belong" at the point in the inheritance chain where it has to be to be accessible to all concerned classes. In this case, delegation seems to be a better solution, an instance variable holds an object of the "appropriate" class, (the one that "ought" to own the method), and a local method simply passes the message to that object. Perhaps multiple inheritance can provide an out, where one inheritance path is "is-a" while another represents some other relationship. The "gracefulness" seems to rely on something a bit stronger than "coincidental coupling". -- Joe Davison ihlts!joed IH 6N-424 x4920
bpe@brunix (Page Elmore) (05/18/89)
If you have the types such as: Type Car ^ Property: color: allColors (type with instances of all colors) | Method: Paint(newColor: allColors) | | Type Honda CRX (inherits from Car) Property color: blueBlack (type with blue and black instances) where Type Car defines a property color which has as it's value type allColors and also has a method Paint which takes as its argument an instance of allcolors. Somewhere in the implementation of paint it sets the color of the car to the newly painted color -- an instance of allColors. Now what happens when someone invokes the Paint method with red as the argument on members of a set of Cars, some of which happen to also be Honda CRX's (this is invoking the rule that instances of the subtype are also instances of the supertype)? The Paint method will fail on those objects which are Honda CRX's. Now you will probably say that you should redefine Paint on Honda CRX as Paint(newColor:blueBlack) and only accept the correct colors. This only makes matters worse however because now you have "broken" the code which was trying to paint the set of cars (some of which are Honda's) some new color. You can cheat and have the Paint method just do nothing when the color is wrong, but then you are not building a strongly typed system (which is another discussion altogether). No matter how you do it, constraint of any of the signatures of a supertype in the subtype may "break" the implementation of the supertype or anything written to work on instances of the supertype. To me the modifications we term classification can be partitioned into two parts: one kind of modification which contains constraints like those above, and are modeled in systems such as the Semantic Data Model (Hammer and McLeod, ACM Trans on DBS, Sept, 1981), and the other which can be modeled with inheritance.
labc-4da@e260-4d.berkeley.edu (Bob Heiney) (05/18/89)
In article <6685@brunix.UUCP> bpe@cs.brown.edu (Page Elmore) writes: >If you have the types such as: > > Type Car > ^ Property: color: allColors (type with instances of all colors) > | Method: Paint(newColor: allColors) > | > | > Type Honda CRX (inherits from Car) > Property color: blueBlack (type with blue and black instances) > > >where Type Car defines a property color which has as it's value type allColors >and also has a method Paint which takes as its argument an instance of >allcolors. ... >The Paint method will fail on those objects which are >Honda CRX's. > >Now you will probably say that you should redefine Paint on Honda CRX >as Paint(newColor:blueBlack) and only accept the correct colors. This >only makes matters worse however because now you have "broken" the code >which was trying to paint the set of cars (some of which are Honda's) >some new color. ... Here we have a conflict that seems to me to be coming from expecting to impose too much generality on a situation. In trying to constrain everything to just one metaphor, we're confusing ourselves. Certain problems work well with inheritance as specialization of a general class. For example, all windows in a window system need to be able to move to an x,y coordinate, draw themselves, etc. Here plain old inheritance works fine. The windows have compatible features. But sometimes we want to inherit from a class not because we want to be compatible with the class, but because we want to extend its services. For example, BI_LINKABLES which implement two way lists in Eiffel inherit from LINKABLES. Now it doesn't make any sense to link a LINKABLE to a BI_LINKABLE list, so the function that makes links is more specific in BI_LINKABLES than LINKABLES. I guess you could say that LINKABLES are really a "subset" of BI_LINKABLES, but then you still can't link to the left. The reason we want to inherit from LINKABLE is not that we want to be treated as LINKABLEs, but that most of the features of a LINKABLE are things we'll need. We're inheriting for ease of coding, not for classification. Our client is the LINKED_LIST package (only) and it "knows" about the services a BI_LINKABLE offers. It needs to know something, whereas a window system could care less about the features a particular window offers for the most part. Gettting back to our Hondas, we need to consider how we're using this class. Since with this example we do seem to want classification, we might provide another feature in addition to Paint. A query function, Paint_Available(color:allColors) : Boolean would be good. Then a client can ask a Honda if you can paint it a certain color, and then a precondition of Honda's Paint would be: Paint_Available(color). In otherwords, if a feature is a desired property of all descendants of a class, but not compatible in all descendents of a class, the base class should provide queries. If a feature is "like" (note Eiffel keyword) one in one of its ancestors, but isn't compatible, assume that the client knows what it's doing, like BI_LINKABLES do. We're confused because we're using one structure to represent both simile and metaphor, both "IS_LIKE_A" and "IS_A". A HondaCRX IS_A car, but a BI_LINKABLE IS_LIKE_A LINKABLE. Hope I didn't muddy that waters too much, ------------------------------------------------------------------------------- | Bob Heiney "And in the end, the love you | | labc-4da@rosebud.Berkeley.edu take is equal to the love you make." | | -- The Beatles | -------------------------------------------------------------------------------
wlp@calmasd.Prime.COM (Walter L. Peterson, Jr.) (05/18/89)
In article <329@odi.ODI.COM>, dlw@valens.odi.com (Dan Weinreb) writes: > > In just about all of the popular object-oriented languages, an object > is a direct member of exactly one class. So if you need special > behavior for frisbee_catchers, then yes, you really do need a special > class. Of course, there's no hope here without multiple inheritance, > since ability to catch frisbees is orthagonal to biological order. > The graph would look like this: > > mammal frisbee-catcher > | | > dog | > | | > \ / > frisb-o-pup > > This is perfectly consistent with the original claim. A frisb-o-pup is a dog, > a dog is a mammal, and a frisb-o-pup is a frisbee-catcher. Most examples of multiple inheritance use some-what artificial examples like the one above, and like the one above, most are inappropriate for multiple inheritance. The above example is mixing the paradigm upon which the hierarchy is built. The "mammal-dog" hierarchy uses a taxonomic (in more than just the biological sense) or "is-a" paradigm. The "frisbee-catcher" is not, in that paradigm, a thing that a dog (or other mammal) *IS*; it is something that it *DOES*. Part of the problem comes from the fact that the class "frisbee-catcher" just seems to appear out of space. If we think a bit about what the class "frisbee-catcher" is we can flesh-out that part of the hierarchy and find a more appropriate way of describing a frisbee catching dog. Since the "mammal" hierarchy is an "is-a" hierarchy, we should have a corresponding "is-a" hierarchy into which to fit the class "frisbee-catcher". Since catching frisbees is something that an animal (we'll exclude robots for simplicity) does, we could call it a "skill". This gives us two sub-hierarchies (both of which would be under some super class such as "class" or "object") that are consistant in their use of the hierarchical paradigm in that they are both "is-a" hierarchies. Philosophical consistancy is not the only reason for doing this, however. The simple example above is concerned with only one 'kind' of dog; a "frisb-o-pup". But what happens when we add "sheep-dog", "guide-dog", "guard-dog", etc. ? The example in the original posting would then require a distinct class for each of these, each multiply inheriting not only from "dog" but from "shepherd", "guide", or "guard". The resulting hierarchy graph would be such a mess that I don't even want to think about trying to 'draw' it here. On the other hand, the graphs of the two separate sub-hierarchies is clean and easy to represent even in this medium. The hierarchies (omitting the 'higher' "class" or "object" classes) might look like this: mammal skill ------ ----- | | --------------- ... ---- -------------------------- ... ---- | | ... | | | ... | cat dog ... aardvark frisbee-catcher shepherd ... guide --- --- ... -------- --------------- -------- ... ----- (For purists: Yes, there should be several levels of classes between "mammal" and "dog", "cat", etc. ). The connection between instances of class "dog" and instances of class "skill" can easily be made by having an attribute (or field as some say) for 'skill' in the definition of the class "dog". This would be an appropriate attribute for a dog, since we train dogs to have such diverse skills. This raises a further problem with the example; what do we do in the case of a multi-skilled dog? If a "sheep-dog" is also a good "frisbee-catcher" do we create a "frisb-o-shep-o-dog" class that inherits from "dog", "frisbee-catcher" and "shepherd" ? I think not. If we follow the alternative presented here, we can make the skill attribute in class "dog" an aggregate such as a set or an array, thus allowing an instance of class "dog" to not only have multiple skills, but also allowing us to add skills as we train fido. The separation of the two hierarchies also allows us to add new skill classes as needed. This would allow the class "dog" to be defined as something like: DEFINE dog IS SUBCLASS OF mammal FIELDS name : string skills : set OF skill . . . (I KNOW that isn't C++ syntax (its DSM). The idea of what is being done is what is important here, not the implementation details of a specific language.) The various skill classes would have fields appropriate to the specific skill in question. We can then create instances of "dog" for fido, rover, etc. creating instances of the appropriate skill classes for each individual animal and assigning appropriate values to the fields of the various instances. Part of the problem with multiple inheritance is finding good cases for its proper use. Most of the examples that I have seen, including the original posting above, have the common problem of being too contrived and more appropriately solved by other means. The only example that I have seen that is both simple and from a familiar problem domain to be used as a general example is from biological taxonomy. That example is the case of the true 'hybrid', such as a mule. A mule, which is the offspring of a donkey and a horse, is neither a "kind_of" horse nor is it a "kind-of" donkey; however, its attributes are inherited ( in both the biological and object-oriented sense ) from both "horse" and "donkey". I have seen a few very good examples of the appropriate use of multiple inheritance in CAD/CAM applications, but they are far too complex and are from too specialized a problem domain to be useful for general examples. Inappropriate use of multiple inheritance can lead to convoluted, overly complex object models that fail to do what they are meant to do: map the problem domain into a program. Multiple inheritance is a very useful thing to have available in an object-oriented language, but it has a tremendous potential for abuse. -- Walt Peterson. Prime - Calma San Diego R&D (Object and Data Management Group) "The opinions expressed here are my own and do not necessarily reflect those Prime, Calma nor anyone else. ...{ucbvax|decvax}!sdcsvax!calmasd!wlp
bpe@brunix (Page Elmore) (05/18/89)
I agree with Bob Heiney's comments -- we are confusing matters by mixing subsets with subtypes via inheritance. I would say that we need a mechanism for is-a inheritance which preserves substitutability, and one for subset which allows classifiction. Page Elmore Object-Oriented Database Programming Language Project Brown University Computer Science Department
jss@hector.UUCP (Jerry Schwarz) (05/19/89)
These comments refer solely to my approach to using inheritance in C++ as I am not familiar enough with Eiffel or Smalltalk to make informed comments. When designing a class it is important to consider what I call the "protected interface". This is the interface between the class and classes derived from it. Part of this specification is what relationship there will be between the abstractions represented by the base class and those represented by the derived class. I know this goes against the "Object Oriented religion", but I believe that derivation should be done only from base classes that were designed with derivation in mind and then only in the way that was intended when the base class was specified. One possible relation between base and derived class is subsetting. That is derived classes should represent subsets or the set of objects represented by the base class. (This seems to be the intention of the "is-a" advocates.) Another relation (which I use frequently) is that derived classes should represent the same things as the base class but with differing implementations. This is particularly common when the base classes is "abstract", i.e. has "dummy" virtual member functions that must be supplied by derived classes. (In 2.0 there is syntax to allow this condition to be enforced by the compiler) Jerry Schwarz AT&T Bell Labs, Murray Hill
tuck@jason.cs.unc.edu (Russ Tuck) (05/19/89)
Let me say first that I agree with the main points of the article quoted below. However, the particular example chosen can be changed to avoid the problem and better correspond to "reality". In article <6685@brunix.UUCP> bpe@cs.brown.edu (Page Elmore) writes: >If you have the types such as: > > Type Car > ^ Property: color: allColors (type with instances of all colors) > | Method: Paint(newColor: allColors) > | > | > Type Honda CRX (inherits from Car) > Property color: blueBlack (type with blue and black instances) > >where Type Car ... >also has a method Paint which takes as its argument an instance of >allcolors. Somewhere in the implementation of paint it sets the color of >the car to the newly painted color -- an instance of allColors. Now >what happens when someone invokes the Paint method with red as the argument >on members of a set of Cars, some of which happen to also be Honda CRX's >(this is invoking the rule that instances of the subtype are also instances >of the supertype)? The Paint method will fail on those objects which are >Honda CRX's. If you have a real Honda CRX and pay a body shop to paint it red, what happens? Do they say "I'm sorry, we can't do that. Honda CRXs can only be black and blue"? No, they paint it red and you have an unusual red Honda CRX that can't be bought directly from Honda. I think the most natural way to define Honda CRX above is to give it a constructor that enforces the restriction that all Honda CRXs are *initially* black or blue. The color property is inherited unchanged (not restricted). >No matter how you do it, constraint of any of the signatures of a supertype >in the subtype may "break" the implementation of the supertype or anything >written to work on instances of the supertype. Good point. -- Russ Tuck internet: tuck@cs.unc.edu Computer Science Dept., Sitterson Hall csnet: tuck@unc University of North Carolina uucp: ...!mcnc!unc!tuck Chapel Hill, NC 27599-3175, USA Phone: (919) 962-1755 or 962-1932
jss@hector.UUCP (Jerry Schwarz) (05/19/89)
In article <327@calmasd.Prime.COM> wlp@calmasd.Prime.COM (Walter L. Peterson, Jr.) writes: >Part of the problem with multiple inheritance is finding good cases >for its proper use. Most of the examples that I have seen, including >the original posting above, have the common problem of being too >contrived and more appropriately solved by other means. The only >example that I have seen that is both simple and from a familiar >problem domain to be used as a general example is from biological >taxonomy. Here is an example that I think satisfies the above request. It isn't contrived, its simple, and its a familiar "problem domain". The iostream library that is scheduled to be part of 2.0 uses mulitple inheritance in the following way: class ios { ... } class istream : virtual public ios { ... } ; class ostream : virtual public ios { ... } ; class iostream : public istream, public ostream { ... } ; Or in pictures ios / \ / \ / \ istream ostream \ / \ / \ / iostream Input operations are allowed on istreams. Output operations on ostreams. iostreams allow both operations (as when updating disk files) The abstract model of a stream is a sequence of characters with a pointer. istream, ostream, iostream operate on the same abstraction but allow different operations. This improves compile time type. checking. (E.g. attempts to input from cout are caught at compile time.) Jerry Schwarz AT&T Bell Labs, Murray Hill
gjditchfield@watmsg.waterloo.edu (Glen Ditchfield) (05/19/89)
In article <6743@brunix.UUCP> bpe@zaphod.UUCP (Page Elmore) writes: >I agree with Bob Heiney's comments -- we are confusing matters by mixing >subsets with subtypes via inheritance. I would say that we need a mechanism >for is-a inheritance which preserves substitutability, and one for subset >which allows classifiction. I would put it this way: we should have an inheritance mechanism for code reuse, and we should have a specification mechanism (perhaps like Russell signatures) for substitution. The last means that, if a function wants to add two parameters, it should specify that the parameters have a '+' operation. It should not specify that they must inherit from Number, and thereby risk excluding strings. Alphard did this mostly right, back in the mid 70's. Alphard let you say things like function f( p1, p2: ?T< plus(T,T) returns T > ) which means that f has two parameters that can be of any type, as long as that type has a "plus" function. (Read ?T as "forall T".) Alphard also promised data abstraction, inheritance, generic types, and an elegant approach to iterators that is quite unlike the CLU-style coroutine approach. It was a sad day when the Alphard team decided not to produce a compiler... Glen Ditchfield gjditchfield@violet.uwaterloo.ca Office: DC 2517 Dept. of Computer Science, U of Waterloo, Waterloo, Ontario, Canada, N2L 3G1 waddles like-a Duck & quacks like-a Duck => is-a Duck
rich@kappa.Rice.EDU (Richard Murphey) (05/20/89)
> Here is an example that I think satisfies the above request. It isn't > contrived, its simple, and its a familiar "problem domain". > > The iostream library that is scheduled to be part of 2.0 uses > > class ios { ... } > > class istream : virtual public ios { ... } ; > > class ostream : virtual public ios { ... } ; > > class iostream : public istream, public ostream { ... } ; > > Input operations are allowed on istreams. Output operations on > ostreams. iostreams allow both operations (as when updating disk > files) > [picture omitted] > > Jerry Schwarz > AT&T Bell Labs, Murray Hill For the novice C++ programmer iostreams seems like one of the most attractive features of C++. Your example works fine with g++-1.35.0! #include <stream.h> #define nl <<"\n" #define sp <<" "<< class iostream : public istream, public ostream { iostream(); }; iostream::iostream() :istream(stdin), ostream(stdout) { } int main(int argc, char *argv[]) { char a[80]; iostream h; h << "test" nl; h >> a; h << "got" sp a nl; return 0; } Script started on Fri May 19 14:56:23 1989 rich@kappa /usr1/rich/pub/135/t> make t g++ -g -Wall t.C -o t -lm rich@kappa /usr1/rich/pub/135/t> t test stuff got stuff rich@kappa /usr1/rich/pub/135/t> exit rich@kappa /usr1/rich/pub/135/t> script done on Fri May 19 14:56:58 1989 I don't claim to understand Multiple inheritance well, but this instance of it does seem useful as you suggest. Regards, Rich Murphey Electrical Engineering Rice University rich@rice.edu
fchan@watvlsi.waterloo.edu (Francis Chan) (05/20/89)
It seems we have a consensus that there are two ways to look at when to sub-classify: One is the knowledge based approach of sub-classing when there is a case of IS-A or LIKE-A relations between the super-class and sub-class; e.g. the good old "Dog IS-A mammal" example. The other view is the code-sharing approach which promotes sub-classing if how a message is handled in the sub-class instance is different from that of the super-class instance; e.g. the "LINKABLES" and "BI-LINKABLES" objects handling of say, an "unlink" message. I guess it all boils down to what your application is intended to do. If you are doing a knowledge base, then the former sub-classification scheme seems pretty amenable whereas if you are doing a user-interface, the latter scheme may be more appropriate. But in general, it seems that both type of schemes tend to overlap alot. And if it doesn't you can make it so by abstracting to an even higher level e.g. BI-LINKABLES and LINKABLES are sub-classes of LINKOBJECTS and instances of LINKOBJECTS have a body while instances of LINKABLES have a body (inherited from LINKOBJECTS) as well as links to the right, etc. Thus, filling the body would be handled by methods inherited from the LINKOBJECTS class while linking and unlinking would be handled specifically by the sub-class methods. Common code pieces (read common code segments within methods) could be handled as methods belonging to a super-class. I have some problems with the Car & Honda CRX examples... The very fact that class Car was defined to come in a number of colors, then that's all the colors any sub-class of Car will have. It is very constraining, true but that is the whole idea of Object-Oriented Programming in the first place (or have I got that wrong..). It is to provide you with a very clean interface and also the added protection via encapsulation. The fact that there are more colors, and that a paint shop can paint your Car a different color doesn't matter. All that means is that the specification of Car is incorrect. What you could say is that Car has a value that is a member of the class CarPaint. Meanwhile the class CarPaint will specify what colors are available. Higher level classes should not be specific.... let the sub-classes do that. Be specific only if you know that all the sub-classes will not affect the value in any way. Most of us are having classification problems because we aren't taught to program in that mode. If your very first computer language was object-oriented and you have been using it for awhile (love these non-specific words), abstraction would probably be the easiest thing in the world. Global variables ... aieee! Avoid like the plaque! Modularization is the Law! Well that's my 2 cents worth... Please note, no flames intended if that's what they appear to be. All this is my view point of the sub-classing issue. FLAME-RETARDENT suit is on. Francis Chan
jss@hector.UUCP (Jerry Schwarz) (05/20/89)
I gave as an example: The iostream library that is scheduled to be part of 2.0 uses class ios { ... } class istream : virtual public ios { ... } ; class ostream : virtual public ios { ... } ; class iostream : public istream, public ostream { ... } ; Input operations are allowed on istreams. Output operations on ostreams. iostreams allow both operations (as when updating disk files) In article <3297@kalliope.rice.edu> rich@kappa.Rice.EDU (Richard Murphey) presents a similar example class iostream : public istream, public ostream { iostream(); }; iostream::iostream() :istream(stdin), ostream(stdout) { } The idea of pairing an istream with an ostream in this way is interesting, but it is subtly different from my example. The 2.0 iostream library puts the "data" into ios, so that an iostream has only one set of data. If Rich had done something that tried to examine the error state (for example) he would have encountered difficulties because there are two such in his iostream. Jerry Schwarz AT&T Bell Labs, Murray Hill
wlp@calmasd.Prime.COM (Walter Peterson) (05/23/89)
In article <4369@watvlsi.waterloo.edu>, fchan@watvlsi.waterloo.edu (Francis Chan) writes: > It seems we have a consensus that there are two ways to look at ^^^^^^^^^ > when to sub-classify: No, I would not say that we have reached a consensus. ("1. general agreement, 2. group solidarity in sentiment and belief" -- Webster's 9th Collegiate Dictionary). In fact, we are far from it; the mere presentation of opposing points of view does not constitute a consensus. > > One is the knowledge based approach of sub-classing when there is a case ^^^^^^^^^^^^^^^^^^^^^^^^ > of IS-A or LIKE-A relations between the super-class and sub-class; e.g. > the good old "Dog IS-A mammal" example. > > The other view is the code-sharing approach which promotes sub-classing ^^^^^^^^^^^^^^^^^^^^^ I have some serious reservations about both of these terms. First, the idea that the IS-A hierarchy is restricted to knowledge based applications is not strictly correct. The IS-A hierarchy with its HAS-A relations certainly is a significant part of semantic networks and other forms of knowledge representation, but it has nearly universal applications. The very term "code-sharing" worries me. In object-oriented programming code is NOT shared, *BEHAVIOR* is shared; the mechanism for sharing behavior is inheritance. Let me go back to the case of the frisbee-catching dog that got me going on this topic. In that example, the oringinal poster had a class called "frisb-o-pup" multiply inheriting from both class "dog" and class "frisbee-catcher". My objection to that scheme was based on all of the different behaviors that dogs can perform. That scheme would have separate classes for "frisbee-catching" dogs, "guide-dog" dogs, "shepherd" dogs. It would be further complicated by a sheep dog that was also a good frisbee catcher (on his day off from herding sheep); would that be an instance of the class "frisb-o-shep-o-pup" ? Lets further complicate the scheme. People also catch frisbees, some better than others. How would we represent Joe Blow, a programmer, programming manager, Vietnam veteran, world-class amatuer frisbee player, etc. ? I hope that everyone can see that it would be ridiculous to create a class that inherits from "person", "programmer", "manager", "frisbee-catcher", etc. Joe IS-A person; he *DOES* or *HAS* these other attributes. This may sound like a knowledge representation or knowledge base scheme, but what if the application that we are talking about is the personel system from Joe's company ? Not quite what one thinks of when one hears the term "knowledge base" is it ? > .... > > I guess it all boils down to what your application is intended to do. > If you are doing a knowledge base, then the former sub-classification > scheme seems pretty amenable whereas if you are doing a user-interface, > the latter scheme may be more appropriate. The IS-A hierarchy is totally appropriate for a user interface. What kinds of objects are we dealing with in a user interface ? Windows, buttons, "volume" controls ( like the bell loudness on a MacIntosh ), icons, screen coordinates, etc. A text window IS-A window as is a graphics window; the bell loudness IS-A "volume" control as is the screen brightness control and so on. A user interface is no different, conceputually, from any other application they all have objects that have behaviors and/or attributes. The fact that some of these objects may be more or less abstract than others is really immaterial. In the knowledge representation realm the object-oriented programmer may be faced with the problem of representing philosophical or religious beliefs, which are far from being what most people think of as being "objects" but they can still be represented in the object-oriented paradigm, just as well as cars, trucks frisbee-catching dogs and programmers. My own experience with object-oriented programming is in the area of CAD/CAM systems and the data management systems to support them. While there are some elements of knowledge representation in CAD, as there is in ANY program, it is not a true knowledge representation application. > But in general, it seems > that both type of schemes tend to overlap alot. And if it doesn't you > can make it so by abstracting to an even higher level e.g. BI-LINKABLES > and LINKABLES are sub-classes of LINKOBJECTS and instances of > LINKOBJECTS have a body while instances of LINKABLES have a body > (inherited from LINKOBJECTS) as well as links to the right, etc. Thus, > filling the body would be handled by methods inherited from the > LINKOBJECTS class while linking and unlinking would be handled > specifically by the sub-class methods. Common code pieces (read > common code segments within methods) could be handled as methods > belonging to a super-class. Why is there an alarm bell going off in my head and a sign flashing the word "Kludge" ? This statement really worries me. It worries me because this is NOT the only time or place that I have seen it. In fact, I have seen it all too often. Lets all remember that object-oriented design and programming ALLOWS us to design and write better software; it does not and can not PREVENT us from writting BAD code. > > I have some problems with the Car & Honda CRX examples... You are not alone; I have a LOT of problems with it. > > The very fact that class Car was defined to come in a number of colors, > then that's all the colors any sub-class of Car will have. It is very > constraining, true but that is the whole idea of Object-Oriented > Programming in the first place (or have I got that wrong..). You have it partly wrong. Part of the idea of object-oriented programming is to constrain us to use pre-written classes ( written by the compiler designer, our project team members, or even by ourselves ) correctly. This requires some forethought. Object-oriented programming should (note: *should*) stop or limit the all too common practice of throwing something at the computer and beating on it until it "works"; sort-of. The Car - Honda CRX and the frisbee-catching dog example are very poorly thought out, as is shown by the problems with representing a simple paint job, or a multiply talented pooch. Thought and an appreciation of the problem being modeled are what we need in OOPS, not patches. > > The fact that there are more colors, and that a paint shop can paint > your Car a different color doesn't matter. All that means is that the > specification of Car is incorrect. What you could say is that Car has a > value that is a member of the class CarPaint. Meanwhile the class > CarPaint will specify what colors are available. Higher level classes > should not be specific.... let the sub-classes do that. Be specific > only if you know that all the sub-classes will not affect the value in > any way. > Close, but not quite there yet. Lets agree that all cars have an attribute called COLOR (COLOUR for our friends in the U.K.). Now, what, based upon our common knowledge of cars, can we say about car colors ? First of all we can say that ALL cars have color ( at least I have not seen a totally transparent cars ). From this we can say that the color attribute should be defined on the class CAR rather than on its sub-classes (we may actually define COLOR on some super-class of CAR, like VEHICLE, but for now lets not). Now the question is: should we constrain COLOR or not and if we do, how ? A quick look arround the streets of Southern California tells me that cars can come in all colors of the spectrum. So a VERY broad constraint might be desirable. We could have a VERY large enumerated type called 'color' that would have thousands of color names as its constants, but that would be rather cumbersome. We would have to take into account all of the odd "designer" colors like 'Candy Burgundy Sparkle'; it is a daunting thought to even consider any resonably broad enumerated type for car colors. We can solve the problem by allowing the COLOR attribte for cars to be a simple character string. Now we need to consider another factor about car colors; two- three- and more tone cars are frequently seen. How do we handle this ? The simplest way is to define COLOR as an aggregate, specifically a set, since we would only want "blue" to appear once for each blue car. So how would we deal with the model-T, which came "in any color you want so long as it is black" ? THAT is a job for the sub-class "model-T". "model-T" can have its own color methods that over-ride the more general methods on CAR. These over-ride methods constrain model-Ts to all be INSTANTIATED as black. Note the emphasis on INSTANTIATED; since customizing cars is so common and so easy, methods should be allowed to change the color, upholstery, wheels, etc. To facilitate keeping track of these changes we might consider adding a boolean CUSTOMIZED field to class CAR so that all instances of it and its sub-classes could tell us if they have been modified. Things that are never or very seldom customized, like the frame would not have methods that would allow us to customize them. We could add a CUSTOM-CAR sub-class to CAR that would allow for the really weird ones. We can do a lot of things to improve this class hierarchy definition; there is no one right answer. There are however any number of WRONG answers. Most of the solutions for the CAR hierarchy that have been posted here are wrong. > Most of us are having classification problems because we aren't taught > to program in that mode. If your very first computer language was > object-oriented and you have been using it for awhile (love these > non-specific words), abstraction would probably be the easiest > thing in the world. Global variables ... aieee! Avoid like the > plaque! Modularization is the Law! *mount soapbox* Most of us were never *taught* anything useful ( 1/2 :-) ). The most important thing that anyone ( and I might say programmers in particular ) can be taught is how to THINK. I have seen too much reacting, too much copying what others have done and far too little originality and creative thought on the part of programmers to believe that our university computer science departments are doing a very good job in this area. If your first language was an object-oriented language, you may actually be at a disadvantage; you don't know just how bad things can be ! My first language, 17 years ago, was assembly; believe me, I KNOW how bad it can be. I can appreciate object-oriented programming and the potential which it represents. That potential will only be realized if designers and programmers use it properly, and that requires thought. The bad designs that I have seen here do not so much represent a lack of understanding of the object-oriented paradigm as they do a lack of thought about the consequences of the design decisions involved. My experience with about 3 years of object-oriented programming tell me that OOPS requires more thought than other design and programming paradigms. That increase in thought WILL be worth it. If you spend the time 'up-front' to think about your design, the detailed design and coding of the methods will be easy. Next time you ( that is all of us, not just the author of the posting being followed up here ) have a design problem; STOP. Put the keyboard out of the way. Put the pencil and paper away. THINK. Then put it down on paper as an object model ( you can use the keyboard if and only if you have the necessary CASE software to draw your model on the screen ). See if you have created any problems for youself like the Honda CRX or the "frisb-o-shep-o-pup". If you have, DO NOT try to "patch" it up or find work-arrounds; RE-DESIGN you object model. Start that redesign by going back to step one: THINK. *dismount soapbox* -- Walt Peterson. Prime - San Diego R&D (Object and Data Management Group) "The opinions expressed here are my own."
kasper@iesd.dk (Kasper Osterbye) (05/24/89)
Then here is my N cents of ideas for this discussion. First I believe that inherritance are used for two things. It is used for classification in descriptions of real world entities, eg. the IS-A relation. This is an important use especially when seen in connections to systems build around the JSD architecture, which is also advocated by Meyer (though not so explicitly). JSD is Jackson System Development method, described in Michael Jackson, System Development, Prentice-Hall, 1983. Highly recommended! Second inheritance is used for code-sharing (or behaviour-sharing if that is a less upsetting word). This can be seen in Smalltalk where Semaphore is a subclass of linked list. My observation is that both of these views are important, but they unfortunately get in the way of each other. I see some solutions to this. Solution number 1. This example is along the lines of the frisbee-catching-brown-sheepdog. In flavors they have a notion of mixin's. (I am not very familiar with Flavours, and got the idea from elsewhere, but the word mixin is good for this example). If we take a class car, we can write a number of classes that describe various features that can be added to cars. PowerSteering, Sunroof, CarStereo, automatic shift etc. When I need a car, I make a subclass of car, MyCar. But in making that subclass I can use anynumber of mixins I care for (and can pay for). If I make a Car1 with stereo and a Car2 with Sunroof. If I now want a car with both stereo and sunroof, I do not make a subclass of Car1 and Car2, but rather a new subclass of Car, Car3 with the stereo and sunroof mixins. An other classical example of multiple inherritance is the window example, where we have window, titlebars, scrollable windows and boardered windows. Again, the titlebar, scrolling, and boarder can be made features/mixins to be used in for concrete windows. Now what are these secondary classes in the inherritance. In my examples they do not represent the IS-A relation, but rather the HAS-PART relation. They are used as aggregation rather than specialization. Also a very good example is that it makes sence to inherrit from the same mixin twice (or more), I might want to add two sunroofs in my streched limosine. Or a person could have two programming jobs, thus being a programmer twice. Also these special classes like sunroof are not intended as stand alone classes, and they can normally not be used as mixins for other classes. (A horse or a bike with sunroof?). That is they are intended to serve as PARTS. Solution number 2. One could considder a language where we had a specification of the class in some higher level language. These specifications could be organized in a hierarchy also, but not necessaly following the class hierarchy. The hierarchy of the specifications could then be made to follow the IS-A relation, as there is no need for code-sharing in specifications. And the classes could be organiced in a code-sharing hierarchy. I have not thought about this solution as much as the other one. For anyone interested, I am working on a paper of the first solution, that I can send/mail (its in latex). Regards, Kasper. -- Kasper Osterbye Internet: iesd!kasper@uunet.uu.net Institute for electronic systems UUCP: ...!mcvax!iesd!kasper Strandvejen 19, 9000 Aalborg DENMARK. (W) +45 98 13 87 88-285 (H) +45 98 37 30 65