Question on inheritance vs. delegation 
Author Message
 Question on inheritance vs. delegation



Quote:
>        The May 1994 issue of BYTE has an article (I've
>forgotten the title) by Pountain and Szyperski (author of
>Oberon's Write) about extensibility in operating systems.  One
>of their main points of discussion was the issue of
>inheritance vs. delegation, i.e. subtyping vs. subclassing.  
>        I didn't quite follow them on delegation and its
>strengths.  In fact, I'm not entirely sure what they meant by
>delegation (what I thought was delegation, they called
>forwarding).  Did anyone else read this article?  Would
>someone elaborate a little bit on inheritance vs. delegation?  

They seem to be using "delegation" to refer to the ability
to "pass the buck" to a parent type, without having the parent type
"call back" to the delegating type through invoking a method
that has been overridden.  This sort of "delegation" approach is
somewhat less flexible, but is generally easier to maintain.  

The basic scenario is as follows:  Presume a type T1 (or if you
prefer, a "class" T1) has two operations (methods), A and B.  
Someone decides to extend T1 to create a type T2,
and chooses to override operation A but not operation B (presumably
because they like what op B already does).
This might or might not have the intended effect, because
the inherited op B might make a call to op A,
and depending on whether this call is statically or dynamically
bound, the inherited op B will either do the same thing
it did before, or do something new because A is presumably
doing something new.  Here it is in a simple diagram:

      T1     A  <======   B
                    ^
                    |
      T2   new A <--?== inherited B

Presuming the original B did call the original A at some point,
the question becomes whether the inherited B calls the original A,
or the new A.  In a conventional "inheritance" model, everywhere the
original B called the original A, the inherited B will
call the new A (this implies "dynamic" binding between B and A).  
In what this article calls the "delegation" model,
the inherited B will continue to call the original A, rather than the
new A (essentially a "static" binding between B and the original A).

From a maintenance point of view, what they call the "delegation"
model is simpler, because the original B can be treated as a
"black box" and the extender of T1 need not know whether or
not it called A.  The extender can choose whether or not to
override A independent of whether it chooses to override B.

It is interesting that Microsoft has become a bit of a champion
of a variant of this "delegation" approach (what
they call "aggregation" I believe).  At a recent
OOPSLA, someone from Microsoft was bemoaning the exerience
of maintaining a large class library such as Microsoft Foundation Classes,
because customers had (intentionally or unintentionally) become very
dependent on the particular logic of individual operations of a given
type, and were often upset to find that a new release of the class library
had different "internal" logic, which in certain cases caused their
derived types to break.

The conclusion to draw here is that if conventional inheritance
is used (with the attendant dynamic binding in calls from one
operation of a type to another), then some aspects of this
interdependence are part of the *interface* of the type.
If these aspects of the interdependence change, then the interface of the
type is effectively changed.  On the other hand, with the
"delegation" approach (where effectively static binding is
used in calls between operations of a type) each operation can
be treated as a black box, to be reused as is, or overridden if
it doesn't do what you want.

In Ada 9X, as it turns out, the programmer may choose either
of these approaches, depending on what is most appropriate.  
Static binding is the default in calls between operations
of a type, but dynamic binding (what we call "redispatching" in
this context) is easy to insert if desired.  Here is an example
using Ada 9X notation:

package P1 is
    type Obj is tagged private;

    function Image(O : Obj) return String;
    function Fancy_Image(O : Obj) return String;
      -- Image and Fancy_Image are independent when inherited

    procedure Print(File : File_Type; O : Obj);
      -- Print redispatches to Image by default
private
    ...
end P1;
package body P1 is

    ... (Fancy_Image not shown)

    function Image(O : Obj) return String is
      -- Image and Fancy_Image are independent when inherited
      -- (but we can safely implement one in terms of the other
      --  because we are using static binding)
    begin
       return Fancy_Image(O);   -- Statically bound
    end Image;

    procedure Print(File : File_Type; O : Obj) is
      -- Print redispatches to Image by default
      -- (this means that if Image is overridden, an inherited
      --  Print will change accordingly)
    begin
       Print_String(File, Image(Obj'Class(O)));  
           -- call on Image "redispatches" (is dynamically bound)
           -- because parameter is explicitly converted back to
           -- a class-wide type (denoted by "Obj'Class"),
           -- which is the signal in Ada 9X for dynamic binding.
    end Print;
end P1;

with P1; use P1;
package P2 is

    type My_Obj is new Obj with ...;
    function Fancy_Image(MO : My_Obj) return String;
       -- override Fancy_Image, inherit Image and Print as is
end P2;
package body P2 is
    ...
    function Fancy_Image(MO : My_Obj) return String is  
       -- override Fancy_Image, inherit Image and Print
    begin
       return Fancy_Image(Obj(MO)) & "more fancy stuff";
    end Image;

end P2;

In other OOPLs, it usually also possible to inhibit dynamic binding
(which is generally the default), but the tendency is to use
dynamic binding everywhere.  What this means is that to safely
reuse an inherited operation of a parent type, one normally needs access
to its source code or clear documentation of its "self dependences."

We chose the above approach in Ada 9X, where the programmer needs
to do a little extra to get dynamic binding, to have "black box"
behavior by default, and to make it clear in the source where the
points of redispatching occur -- they will generally look like:

     F(T'Class(param));  -- convert param back to class-wide type to get
                         -- redispatching

In other OOPLs, it is the places of static binding which stick out,
while the places where dynamic binding (redispatching) occur
are somewhat more difficult to spot, and hence somewhat
more difficult to document completely.

Quote:
>Thanks in advance,
>M


Ada 9X Mapping/Revision Team
Intermetrics, Inc.
Cambridge, MA  02138

P.S. The "other" meaning for delegation is typically
associated with "prototype"-based OOPLs, like Self, where
there are no classes/types as such, but an object can
"delegate" to one or more parent objects the handling of
its operations.  However, "redispatching" between operations
on an object is usually still possible in prototype-based
languages, and may even be the default.  In fact, the
name of "Self" comes from the use of the implicit object "self"
to send a message back to yourself, thereby redispatching.

Hence, prototype-based languages are not particularly more
maintainable than type/class-based in the sense discussed
above.  However, prototype-based languages are generally
regarded as more flexible (too flexible for some people's
taste ;-).
  -TT



Sun, 10 Nov 1996 22:18:23 GMT  
 Question on inheritance vs. delegation
        The May 1994 issue of BYTE has an article (I've
forgotten the title) by Pountain and Szyperski (author of
Oberon's Write) about extensibility in operating systems.  One
of their main points of discussion was the issue of
inheritance vs. delegation, i.e. subtyping vs. subclassing.  
        I didn't quite follow them on delegation and its
strengths.  In fact, I'm not entirely sure what they meant by
delegation (what I thought was delegation, they called
forwarding).  Did anyone else read this article?  Would
someone elaborate a little bit on inheritance vs. delegation?  

Thanks in advance,
M



Sun, 10 Nov 1996 20:14:27 GMT  
 Question on inheritance vs. delegation

Quote:



>>        The May 1994 issue of BYTE has an article (I've
>>forgotten the title) by Pountain and Szyperski (author of
>>Oberon's Write) about extensibility in operating systems.
...
>>        I didn't quite follow them on delegation and its
>>strengths.  In fact, I'm not entirely sure what they meant by
>>delegation (what I thought was delegation, they called
>>forwarding).
...
>They seem to be using "delegation" to refer to the ability
>to "pass the buck" to a parent type, without having the parent type
>"call back" to the delegating type through invoking a method
>that has been overridden.  This sort of "delegation" approach is
>somewhat less flexible, but is generally easier to maintain.

Actaully I suspect they're using 'forwarding' for this, and keeping
the original meaning of delegation. I could be wrong.

For another example see the 'CollapsableTreeView' article I posted
a while back, under the 'Inheritance is not necessary' thread.

Quote:
>From a maintenance point of view, what they call the "delegation"
>model is simpler, because the original B can be treated as a
>"black box" and the extender of T1 need not know whether or
>not it called A.  The extender can choose whether or not to
>override A independent of whether it chooses to override B.

>It is interesting that Microsoft has become a bit of a champion
>of a variant of this "delegation" approach (what
>they call "aggregation" I believe).

Oh dear. Can't anyone doing O-O find _unique_ terms for anything?
Aggregation is quite different.

...

- Show quoted text -

Quote:
>The conclusion to draw here is that if conventional inheritance
>is used (with the attendant dynamic binding in calls from one
>operation of a type to another), then some aspects of this
>interdependence are part of the *interface* of the type.
>If these aspects of the interdependence change, then the interface of the
>type is effectively changed.  On the other hand, with the
>"delegation" approach (where effectively static binding is
>used in calls between operations of a type) each operation can
>be treated as a black box, to be reused as is, or overridden if
>it doesn't do what you want.

>In Ada 9X, as it turns out, the programmer may choose either
>of these approaches, depending on what is most appropriate.  
>Static binding is the default in calls between operations
>of a type, but dynamic binding (what we call "redispatching" in
>this context) is easy to insert if desired.  Here is an example
>using Ada 9X notation:

>package P1 is
...
>end P1;
>package body P1 is
...
>    procedure Print(File : File_Type; O : Obj) is
>      -- Print redispatches to Image by default
>      -- (this means that if Image is overridden, an inherited
>      --  Print will change accordingly)
>    begin
>       Print_String(File, Image(Obj'Class(O)));  
>           -- call on Image "redispatches" (is dynamically bound)
>           -- because parameter is explicitly converted back to
>           -- a class-wide type (denoted by "Obj'Class"),
>           -- which is the signal in Ada 9X for dynamic binding.
>    end Print;
>end P1;

Ie. the decision on whether to use redispatching is made in the implementation
of the supertype (P1), at each call to the potentially redispatched method.

An alternative way of doing this is to choose when you override a method,
whether you wish calls to that method in the supertype to be redispatched.
The re-user of the type decides whether the type is to be treated as a
black-box, or whether the binding of internal calls should change.

The advantage of doing it like this is that there are two ways you might want
to extend a type. If you know its implementation, or the pattern of calls to
self within that implementation, then redispatching gives you the most general
way of changing that type with minimal effort.

On the other hand, if you want to wrap functionality around the type without
changing any of its internal operations (essential if those operations are not
documented), you would avoid redispatching.

The point is that the implementor of P1 cannot know which of these alternatives
will be necessary (in fact both may be necessary, in different subtypes).

It's also worth pointing out that with signature typing, you get this for free.
If you don't want redispatch for any methods, just make the parent object an
instance variable in the new type, and delegate all its methods explicitly.
This will give you a subtype without redispatching calls.

I gather the Ada-9X implementation is an inheritance-based version of
forwarding (or whatever it's called). Can anyone tell me of a delegation-based
language in which this is supported?

David Hopwood



Mon, 11 Nov 1996 12:33:41 GMT  
 
 [ 3 post ] 

 Relevant Pages 

1. Inheritance: Interface vs Implementation (was: Questions on inheritance)

2. Delegation-based inheritance

3. Automatic Delegation for Dynamic Inheritance and Convenient Composition

4. Inheritance and delegation problem.

5. STDERR vs $stderr - delegation bug

6. Delegation vs. Inheritance (was SUMMARY: Object-Oriented Software Maintenance)

7. Delegation vs. Inheritance (was SUMMARY: Object-Oriented Software Maintenance)

8. Multiple inheritance VS. Aggrigation..

9. Clients vs Inheritance

10. Inheritance vs Buying

11. Public vs. Private Inheritance

12. Dynamic binding vs. Multiple inheritance

 

 
Powered by phpBB® Forum Software