in reply to Re: Class::Interface -- isa() Considered Harmful
in thread Class::Interface -- isa() Considered Harmful

Inheriting from an abstract base class is dictating an object's implementation. To me, there's a world of difference between saying "even though this mock object inherits absolutely no data or behavior from this class hierarchy, it's a member of this class hierarchy" and "this mock object has the same fingerprint as objects of this class and can be used in their place".

A mock object is not a DBI object. It's not a CGI object. It can act like either though.

I think there's a broader principle here -- substitutability. Inheritance is not the only means by which objects of two different classes can be substitutable. There's also composition and delegation (the "has a" relationship) and equivalence of behavior (the "acts like a" relationship). Making an abstract class would work, but it doesn't express the nature of the substitutability.

I'd like to avoid forcing composition, delegation, and equivalence relationships into the inheritance scheme. If my substitutable object does not fit within the class hierarchy conceptually, why should I have to lie to the compiler and all future maintenance programmers and say that it does?

(I'm very much in agreement with your comments on Java, though. Pity that a good concept sucked up the proper terminology so much that people think I like that approach.)

Replies are listed 'Best First'.
Re^3: Class::Interface -- isa() Considered Harmful
by Aristotle (Chancellor) on Jan 19, 2003 at 19:49 UTC
    I think what you're looking for in place of "fingerprint" is "(interface) contract" - the concept that a class "promises to behave like (f.ex) a CGI object". And indeed, groping around on CPAN and finding Class::Contract, its goals seem to be much like what you are talking about.

    Makeshifts last the longest.

Re^3: Class::Interface -- isa() Considered Harmful
by adrianh (Chancellor) on Jan 19, 2003 at 20:50 UTC
    To me, there's a world of difference between saying "even though this mock object inherits absolutely no data or behavior from this class hierarchy, it's a member of this class hierarchy" and "this mock object has the same fingerprint as objects of this class and can be used in their place".

    To me there isn't. I think that's the basis for my confusion. To me the inheritance hierarchy is the exact way you express the relationship between different objects with the same "fingerprint" (which am reading as contract).

    I don't understand what advantage inventing a different kind of hierarchy gives you.

    A mock object is not a DBI object. It's not a CGI object.

    Why not? Seriously :-)

    Most of my mocks are implemented as full classes and use the @ISA hierarchy to identify themselves.

    I'd like to avoid forcing composition, delegation, and equivalence relationships into the inheritance scheme. If my substitutable object does not fit within the class hierarchy conceptually, why should I have to lie to the compiler and all future maintenance programmers and say that it does?

    I don't see how composition or delegation would come into the inheritance scheme. They're not ISA inheritance relationships. If somebody says a car-driver isa car or a library isa book they're just plain wrong.

    On the other hand equivalence is, to me, what ISA relationships are all about.

    Can you give an example that shows where ISA wouldn't be appropriate?

      Inheritance does two completely different things in Perl.

      First, it expresses a conceptual relationship. This is the hierarchy you mention. If you need to model buildings, a library is a more specific type of building. So is a restaurant.

      Second, it provides a place to look for methods called on the object that aren't defined in the class itself. This is inheritance of behavior.

      There are plenty of other ways to make sure all of the methods are in place. You can define them all yourself. You can set up an AUTOLOAD scheme, as Test::MockObject does. You can use mixins. You can delegate to another object (with a proxy or a bridge). You can contain another object (with composition).

      I can take care of #2 by any of those other methods. That's what my mocks do. Why should I have to rely on inheritance to provide #1?

      I'd rather have a more general solution, one that allows me to say "an object of this type behaves as an object of this other type". If that's in place, inheritance can still work just fine -- derived objects are automatically marked as being able to act as objects of their parent classes.

      That's exactly what Class::Interface does.

      Can you give an example that shows where ISA wouldn't be appropriate?

      A Car object contains for Wheel objects. I have code that wants to find the circumference of all Wheels in my universe, calling get_circumference() to do so. It looks at all of the objects in my universe to see if they can handle the operations a Wheel can handle. Since Car does not inherit from Wheel, it can't check isa(). If Car is marked as having the same fingerprint as Wheel, it will work.

      I'm not interested in any solution that suggests "You should have an abstract base class for hasWheel or delegatesToWheel." I just want to be able to say "Car can handle messages to Wheel" without expressing a relationship that's not there or exposing how the Car handles Wheel messages.

        It seems to be as bad an example as the Airport/Arcade one. The Car doesn't handle Wheel messages. It has four wheels that handle Wheel messages. I don't say let_air_our to the Car, I send that to a specific wheel.

        Makeshifts last the longest.

        A Car object contains for Wheel objects. I have code that wants to find the circumference of all Wheels in my universe, calling get_circumference() to do so. ... If Car is marked as having the same fingerprint as Wheel, it will work.

        But a car has four wheels? Surely $wheel->get_circumference and $car->get_circumference can't be doing the same thing so they don't have the same "fingerprint"?

Re^3: Class::Interface -- isa() Considered Harmful
by adrianh (Chancellor) on May 22, 2003 at 14:31 UTC
    Pity that a good concept sucked up the proper terminology...

    On the terminology front I've just come across the wonderful term Duck Typing to describe what (I think) you're talking about (if it walks like a duck, talks like a duck, etc.).