From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-0.8 required=5.0 tests=BAYES_00,INVALID_DATE autolearn=no autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 103376,1ce9b9f0acc99abe X-Google-Attributes: gid103376,public X-Google-ArrivalTime: 1994-12-09 10:01:26 PST Newsgroups: comp.lang.ada Path: bga.com!news.sprintlink.net!howland.reston.ans.net!news.moneng.mei.com!uwm.edu!lll-winken.llnl.gov!noc.near.net!ray.com!news.ray.com!swlvx2!jgv From: jgv@swl.msd.ray.com (John Volan) Subject: Re: type extension vs. inheritance Keywords: type extension, inheritance Sender: news@swlvx2.msd.ray.com (NEWS USER) Organization: Raytheon Company, Tewksbury, MA Message-ID: References: Date: Fri, 9 Dec 1994 17:04:25 GMT Date: 1994-12-09T17:04:25+00:00 List-Id: RTOAL@lmumail.lmu.edu (Ray Toal) writes: >Hi, > >I have always been under the impression that using C++-style ^^^^^^^^^ (Ahem. Minor quibble: Inheritance was not invented with C++, nor does C++ have a monopoly on the concept.) >"inheritance" (derived classes) should ONLY ONLY be used for >situtations in which an IS-A relationship existed between the >derived class and the base class. > >Ada 9X has "type extension" and in the Rationale I saw an example >where a 3-D box was derived from a 2-D rectangle by adding a >"depth" field to the width and the height. > >1. Is this something one would really do in practice, or was > it just an example to illustrate type extension? I would > be very nervous using derivation for anything other than > inheritance, and certainly a parallelpiped IS-NOT-A rectangle. > Ray, let me just chime in here to say that I wholeheartedly agree: Inheritance is a powerful mechanism for code reuse, but, IMHO, the most "proper" use of it is to represent generalization/specialization ("IS-A") relationships, particularly those arising directly from the problem domain (rather than as artefacts of the solution domain). A true generalization/specializiation relationship satisfies the Liskov Type-Substitutability Principle. This principle requires that any type that is visibly derived from some supertype must fully support the semantics the supertype. Wherever an object of the supertype is expected, we should be able to substitute any object of any type derived from that supertype, and have it behave for all intents and purposes as if it were an object of the supertype. However, many object-oriented practitioners often exploit inheritance purely as a mechanism for reusing code without necessarily satisfying Liskov substitutability. IMHO, this is an "improper" use of inheritance (and perhaps even an outright abuse). Sometimes it's the fault of the language: Programmers might be forced into exploiting inheritance in order to simulate the effect of other constructs (such as Ada's "with" and "use" clauses) that might be missing from the language. But sometimes it's just a symptom of lazy thinking on the part of the programmer. At best, this kind of code reuse should be strictly reserved for "implementation inheritance": In other words, if you're going to reuse the structure and behavior of one type in order to *implement* another type, but the new type does not really support the outwardly-visible semantics of the first, then you should hide the fact that you're using type derivation, by squirelling it away as an implementation detail in a private part. The example you cite of a 3-D box derived from a 2-D rectangle is a glaring example of inheritance without any apparent Liskov substitutability. The fact that it appears in the Ada9X Rationale is very regrettable. The Rationale is a highly-visible document, and as such it's vital (IMHO) that it contain programming examples of the highest possible quality. Two reasons: (1) We want to attract programmers from other OO languages to try out our new-and-improved Ada, so it would be ironic if they were turned off by poor examples that gave them a wrong impression of the language (or its adherents ;-). (2) The Rationale, being so highly visible, has a tremendous potential to influence what (we hope :-) will be a whole new generation of Ada programmers. We'd like to get them off on the right foot. >2. But even if the answer to (1) is "just an example" a better > question is, in industry, how many applications REALLY benefit > from these IS-A hierarchies anyway?? Rosen's paper in the > 1992 CACM Ada special issue on why Ada 83 does not have C++ > style inheritance made a good case for considering classification > secondary to "composition". > >3. And how would one, in Ada 9X, implement in a nice way the > derivation of a square from a rectangle? Am I on the right > track here? > > package Shapes is > > type Figure is abstract tagged record; > procedure Move (F: in out Figure; X, Y: Float); > function Area (F: Figure) return Float is abstract; > type Rectangle is new Figure with private; > function Make_Rectange (W, H: Float) return Rectangle; > function Area (R: Rectangle) return Float; > type Square is new Rectangle with private; > function Make_Square (Side_Length: Float) return Square; > -- area for square inherited from rectangle > > ... This particular example crops up every now and then. I think the going answer (in any language) is that you really don't want to derive "Square" from "Rectangle", because "Rectangle" (as you've defined it) has semantics that are inappropriate for "Square". (For instance, you can make a Rectangle with any arbitrary height and width, but you shouldn't be able to make a Square this way.) Instead, both "Square" and "Rectangle" should be derived from some common abstract type, called, let's say, "Any_Rectangle": type Any_Rectangle is abstract new Figure with private; -- No Make function for this; it's abstract. function Area (R: Any_Rectangle) return Float; -- Overrides abstract Area function inherited from Figure. -- Computes area as Width(R) * Height(R), which it will -- invoke via dispatching calls. function Width (R: Any_Rectangle) return Float is abstract; function Height (R: Any_Rectangle) return Float is abstract; type Rectangle is new Any_Rectangle with private; function Make_Rectangle (Width, Height: Float) return Rectangle; function Width (R: Rectangle) return Float; function Height (R: Rectangle) return Float; -- Area for Rectangle inherited from Any_Rectangle type Square is new Any_Rectangle with private; function Make_Square (Side_Length: Float) return Square; function Side_Length (S: Square) return Float; function Width (S: Square) return Float; function Height (S: Square) return Float; -- Area for Square inherited from Any_Rectangle ... -- In the body, you could just implement Width and Height for -- Square as renamings of Side_Length: function Width (S: Square) return Float renames Side_Length; function Height (S: Square) return Float renames Side_Length; function Area (R: Any_Rectangle) return Float is begin return Width(Any_Rectangle'Class(R)) * Height(Any_Rectangle'Class(R)); -- Casting to the class-wide type causes the function calls to -- dynamically dispatch on the 'Tag of R. end Area; Alternatively, you could just wait until types Rectangle and Square to provide actual Area functions: type Any_Rectangle is abstract new Figure with private; -- Inherits abstract Area function from Figure, -- but that's okay, Any_Rectangle is abstract too. function Width (R: Any_Rectangle) return Float is abstract; function Height (R: Any_Rectangle) return Float is abstract; type Rectangle is new Any_Rectangle with private; function Make_Rectangle (Width, Height: Float) return Rectangle; function Width (R: Rectangle) return Float; function Height (R: Rectangle) return Float; function Area (R: Rectangle) return Float; -- Overrides Area from Figure type Square is new Any_Rectangle with private; function Make_Square (Side_Length: Float) return Square; function Side_Length (S: Square) return Float; function Width (S: Square) return Float; function Height (S: Square) return Float; function Area (S: Square) return Float; -- Overrides Area from Figure ... function Area (R: Rectangle) return Float is begin return Width(R) * Height(R); -- Non-dispatching calls end Area; function Area (S: Square) return Float is begin return Side_Length(S) ** 2; end Area; >Thanks You're most welcome. :-) > >Ray Toal John Volan -------------------------------------------------------------------------------- -- Me : Person := (Name => "John Volan", -- Company => "Raytheon Missile Systems Division", -- E_Mail_Address => "jgv@swl.msd.ray.com", -- Affiliation => "Enthusiastic member of Team Ada!", -- Humorous_Disclaimer => "These opinions are undefined " & -- "by my employer and therefore " & -- "any use of them would be " & -- "totally erroneous."); --------------------------------------------------------------------------------