Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Class::Interface -- isa() Considered Harmful

by chromatic (Archbishop)
on Jan 16, 2003 at 00:53 UTC ( [id://227309]=perlmeditation: print w/replies, xml ) Need Help??

From the POD of Class::Interface:

Polymorphism is a fundamental building block of object orientation. Any two objects that implement the same interface can receive the same methods -- they may be substituted for each other, regardless of their internal implementations.

Much of the introductory literature explains this concept in terms of inheritance. While inheritance is one way for two different classes to provide different behavior for the same interface, it is not the only way. Perl modules such as the DBDs or Test::MockObject prove that classes do not have to inherit from a common ancestor to be polymorphically equivalent.

Class::Interface provides an alternative to isa.

Instead of requiring that objects inherit from an expected class, consider requiring merely that they implement the expected interface.

I think inheritance is overused. Scanning various forums and some of the better programming and design books, it appears that other people agree. Consider two classes -- Airport and Arcade. An Airport can contain an Arcade. This is a composition arrangement.

If we followed the "traditional" inheritance scheme, any code that wanted to make sure it was accessing an Arcade correctly would check to see if the object is an Arcade. This is a problem for our scheme -- there's no good reason to say that an Airport is an Arcade. We might solve this by creating a superclass of both Airport and Arcade, but that's a mess.

I'd personally rather say "Make sure this object can handle any message I can send an Arcade." Since the Airport contains an Arcade, it will just delegate methods like collect_quarters and play_annoying_dance_music to the Arcade it contains.

The code that would otherwise do the checking to see if it's dealing with an Arcade object now does something slightly differently. It now checks to see if the object has declared that it implements the same interface as an Arcade object. It's a slight different of semantics, but it opens lots of other doors.

Of course, if you do subclass Arcade, things work just fine. A subclass automatically implements the same interface as its parent. Or parents. Or any of its ancestors.

I welcome any and all suggestions on the module, from its naming to implementation to tests and documentation.

Update: clarified title per Aristotle's suggestion

Replies are listed 'Best First'.
Re: Class::Interface -- isa() Considered Harmful
by autarch (Hermit) on Jan 16, 2003 at 03:15 UTC

    I think your basic point could be rephrased as this: "Object Oriented Design != inheritance and class heirarchies". This is a good point and well worth repeating. I've seen that a lot (especially newer) programmers want to solve every OO design question with inheritance, even when composition & delegation would be more natural.

    I think one thing that might help would be first class delegation support in a language. I think there's a tendency to feel like inheritance is always okay because its been blessed into the language itself, whereas delegation is just a "hack".

    On a sort of side note, I just read Chris Date & Hugh Darwen's "Third Manifesto" book (2nd edition), which is their response to two previous manifestos aiming to integrate relational theory and object oriented programming/design.

    In the book, they outline a very detailed and well-defined model for inheritance. In their inheritance model, there are no such things as methods. Methods don't belong to a data type (class), they are just functions, with type dispatch based on the type of _all_ the arguments. The first argument is not special, unlike most OO implementations.

    I really like their proposal, except for one thing. It's very data-oriented. If you're using inheritance because you're saying that two things represent a similar kind of data (they share set of properties), it works really well. They use geometrical figures as examples, so a circle inherits from an ellipse, for example. A circle and ellipse both represent similar data (geometric figures), but they don't necessarily have a lot of behavior. When you deal with them you're more interested in data (length of axes, placement on a plane, etc) than asking them to do things for you. And moreover, you want substitutability, so that anywhere you expect an ellipse, you can provide a circle.

    OTOH, there are times when you want OO design in order to provide a common interface to multiple, often quite different, things. For example, if you were creating an RPG game, you might have lots of different objects that need to share some common behavior like "hit a creature". Each object may have fairly different sets of data describing the object, but you want to be able to wack things with them, so you give them a common interface.

    All this is to set up my conjecture which is that maybe we should talk about two kinds of inheritance. For lack of a better word, one would be "behavior" inheritance (all objects must be able to "hit the thing") and the other "property" (data) inheritance (all objects must be able to tell me their weight in kilograms). With property inheritance, you don't care about behavior, but you want to be able to ask for certain information from all objects (weight, length, color, etc.).

    Sometimes you need both, as in the "RPG object" example. But sometimes you might need only one or the other. I wonder if a language which separated the two might be useful.

    In case it isn't clear, this relates to chromatic's piece because I think what I'm calling "behavior" inheritance is the same as chromatic's interface suggestion. My "data" inheritance would be more like object properties. In Perl, properties are not really distinguished from methods (behavior), but they could be!

    BTW, if I'm describing some theory/language, pointers to it are welcome ;)

      All this is to set up my conjecture which is that maybe we should talk about two kinds of inheritance. For lack of a better word, one would be "behavior" inheritance (all objects must be able to "hit the thing") and the other "property" (data) inheritance (all objects must be able to tell me their weight in kilograms).

      I'm close to agreeing with you, and I think the idea can be clarified to answer chromatic precisely. The two types of inheritance are inheritance of interface and inheritance of implementation. The former indicates that the object at hand will respond to the same method calls as its superclass, whereas the latter means that the object will accomplish those tasks using the same code and in the same manner as its parent class. I disagree with the idea that if a subclass merely supports an interface but does not inherit (at least some parts of) its implementation that it lacks an is-a relation with its superclass. A class that implements an interface inherits the public feature set of its parent class and its parent class's type.

      For comparison, look at the concept of the "abstract base class" stemming from C++ and its later evolution to Java interfaces discussed in this recent interview with Scott Meyers, one of the C++ gurus. As a way of avoiding nasty conundrums resulting from C++'s allowance for multiple inheritance, Meyers has argued in his books that a subclass should inherit its implementation from only one of its superclasses. Additional superclasses should be abstract base classes (ABCs) with no data members and only pure virtual functions. In other words, an ABC can at best be instantiated as an empty shell, and its methods declared but not given any bodies and their implementations looked up at runtime in declared subclasses. Thus, all that such classes provide are additional is-a relationships. Java provides shorthand for this with the interface keyword, which guarantees what C++ coders had to see to for themselves. For this reason, placing an interface in a class's @ISA makes perfect sense; it's just rather close to the C++ way.

        Yep, I thought of Java interfaces versus the single "implementation" parent.

        I was thinking of going further. First of all, support Date & Darwen's inheritance model (which is all about inheriting attributes) which includes MI. Define implementation inheritance to not cause the inheritance of implementation. This is what Java interfaces, and chromatic's proposal suggest. Finally, forbid inheritance of interfaces from implementation parents! This last bit is what I think may be a new idea (or at least not present in C++, Java, and certainly not Perl).

        I do think calling it implementation inheritance is a bit confusing, since you're really inheriting attributes (or properties). The actual internal implementation may or may not change in the subclass.

        So if you had a class Car that inherited implementation from WheeledLandVehicle (which may in turn have inherited from some other class), it'd have to provide access to certain attributes such as weight, manufacturer, number_of_wheels, and so forth.

        Interface-wise, Car might inherit interfaces such as Steerable, CanChangeVelocity, Breakable, and so on.

        I should also point out that in the model I'm thinking of, nothing would preclude inheriting both interface and attributes from the same class, which is obviously going to be a common need. But at the same time, when you don't need, or want, to do that, you wouldn't have to.

      This brief article covers some of your points (see 7,8,9) such as (multi)method dispatch, two types of inheritence and inheritence as polymorphism.

      Rees Re:00

      After reading it you may think the OO programming is a little strange.
      Fortunately, perl lets you mix and match your methodologies as appropriate.

Re: Class::Interface -- isa() Considered Harmful
by bart (Canon) on Jan 16, 2003 at 04:35 UTC
    You don't need isa, you need can.

    I often write code of which I assume that an object is able to execute certain methods. When in doubt, I try can() first, before actually calling the method. These classes do not need to be derived from a common class, that is an implementation detail to make virtual classes possible in languages like C++, for example. In Perl, when classes don't have common methods, it doesn't make sense to make them inherit from a common ancestor class.

    I do assume that for those classes, methods of the same name serve a similar purpose, and have the same API. That may be a weak point. Likely so.

      You don't need isa, you need can.

      An interface is a contract. Having a method with a given name isn't. Keeping the conversation at the level of "will this object honor a given contract" (i.e., isa) is preferable to risking false positive based on method names. A baseball player and an airplane both know about "pitch", but the meanings are entirely different.

      You're right that can() is a better approach for protecting against methods that aren't there. I'm working on something at a higher level, though. For example, you don't have to check can() all the time. If you're handed an object, check that it implements the appropriate interface, then keep it around.

      With interfaces, for example, it's easy to mixin a batch of methods from another class -- marked as an interface.

Re: Class::Interface -- isa() Considered Harmful
by Corion (Patriarch) on Jan 16, 2003 at 08:43 UTC

    If one isn't too keen of inheritance and one is not afraid to add dependencies to ones script / module, there also is Class::Delegation, that faciliates containment relationshipts by autocreating the methods that your class delegates to one of its contained elements.

    For a nice example, see CGI::Wiki, which delegates calls to the database backend via this module.

    A reason not to use Class::Delegation is, that it does not work below Perl 5.005 - and my ISP has a borked Perl 5.4.3 (Solaris/gcc), which knows nothing of INIT blocks. If someone knows a nice and transparent way to work around the INIT {} block (or rather, how to have Perl 5.004 not throw a syntax error and to have the module still work / compile under Perl 5.4 and 5.5+ as expected), this would be nice :-) - I've thought about some version dependant eval()s, but haven't come to a conclusion.

    Other than this one-paragraph nit, Class::Delegation helps you avoiding mechanical code that maps method names between your class and the contained object.

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web

      You can write INIT { } as sub INIT { } - that would be the easiest fix. Such INIT blocks will then work as INIT blocks in newer Perls and fulfill the syntax constraints of earlier ones. Alternatively, if you have several INIT blocks, you could define a sub INIT (&) { ... } or similar - however, that should be done depending on version since it will change the semantics of INIT { } for newer Perls as well.

      The question is inhowfar these can be considered to compile "as expected", since the compilation stage specific behaviour is lost. It's in the nature of modules such as this one to require finer control of execution time than what the "blunt" BEGIN block offers, and in that case you're out of luck since old Perls just don't offer that no matter what.

      Makeshifts last the longest.

Re: Class::Interface -- isa() Considered Harmful
by perrin (Chancellor) on Jan 16, 2003 at 17:55 UTC
    I think you're solving a problem that doesn't exist. Java needs interfaces because it has no multiple inheritance. In Perl, you can just make a class that shows the abstract interface, and have everything that implements that interface inherit from it. You don't even need to put anything in the interface class. It can be totally abstract, or it can implement the methods by throwing an error that says "you were supposed to implement this in your class" as I've seen Damian Conway suggest.

    I also agree with castaway that your example doesn't really make sense. An Airport that contains an Arcade does not implement the Arcade interface itself, so of course it won't inherit from Arcade.

      Inheritance is by no means the only way of constructing classes. I'm looking for a scheme by which I can say "this object can receive these messages". I don't want it tied to inheritance, except that I want subclasses to be marked as implementing their parents' interfaces.

      I think Java got it wrong in this case.

      My example (which is not great, and that leads to the confusion), I imagine that the Arcade interface has a couple of methods: collect_quarters() and play_annoying_dance_music(). Whenever a Person walks by, it should play the annoying music. Whenever a FranchiseOwner or Hoodlum walks by, he should be able to collect quarters. It oughtn't matter if the Arcade is alone on the street, in a Mall, or in the Airport. If the actor is near something that implements the Arcade interface, he should have the option of listening to the annoying dance music or collecting quarters.

      If the only option I have is inheritance, that power is lost to me. It's as if I wanted to make a Car that could throw_hubcap(), so Car had to inherit from Wheel. What happens when I want HoverCar?

        It looks to me like you're getting things from inheritance and containers mixed together. An Airport may contain an Arcade, but it would not implement collect_quarters() any more than a Directory class would implement the methods of a File class. If you want to do something with the arcade, you qould query the Airport with a get_arcade() or something, and then operate on the returned Arcade object.

        Same deal with the Car interface: $car->get_wheel->throw_hubcap(). Inheritance doesn't enter it, and neither does sharing interfaces.

        1. The form of multiple inheritance that Java permits is inheritance of interface only. This is not the same as saying that Java provides interfaces in lieu of multiple inheritance.

        2. Subclasses "marked as implementing their parents' interfaces" by definition are built from inheritance.

        3. The only way to "implement an interface" without inheritance -- which is a contradiction in terms because class interfaces convey type -- is to write a class that serves as a wrapper around another class. In this case you've got redundant interfaces leading to the sullying of the outer interface (there's no reason why an Airport should have a public method collect_quarters just in case it contains an Arcade. Are Airports without Arcades different kinds of objects?), when what you really want is what castaway suggested above; namely an accessor that returns a reference to the contained object. The interface of Arcade should contain methods that take a Person or a FranchiseOwner or whatever and decide what to do based upon the type of object passed in.

        All of these examples so far are problems just because the object models themselves are poorly thought out. A Car is not a subclass of Wheel, but more appropriately WheeledVehicle, and a HoverCar is not properly considered a subclass of a normal Car, but probably something like AmphibiousVehicle. As long as such difficulties can be solved by reworking the object model, I don't see any reason to introduce a new and awkward concept of the class interface.

Re: Class::Interface -- isa() Considered Harmful
by castaway (Parson) on Jan 16, 2003 at 12:32 UTC
    I'm not sure I understand what your getting at.

    The example with Airport and Arcade is strange for an example of inheritance/bad inheritance. The two are not related in this way, in OO parlance, an Airport is not an Arcade, we usually say 'an Airport _has an_ Arcade'. (Contains an object of type 'Arcade')

    Otherwise, I agree that interfaces are 'a good thing', I think the Java way of doing things is good. There you can create functions which take any object which implements a certain interface, and its simple to make a class to extend an interface.

    But I digress, I will go see what your class does now :)
    C.

      Hmm, the POD in your module has more of the same.. I see what you're getting at, kind of. Usually if I have to implement such a thing as in your examples, I wouldnt dream of saying that Airport inherits from Arcade just to make the methods of Arcade available in Airport.. I can't see why anyone would.
      I'd create an Arcade object in Airport, and provide something like a 'getArcade()' method in Airport, for anything that needed to communicate with Arcade through Airport.

      Regardless.. Interfaces are good for other reasons, as some people here have already indicated..

      C.

Re: Class::Interface -- isa() Considered Harmful
by adrianh (Chancellor) on Jan 19, 2003 at 17:33 UTC

    Since nobody else has mentioned them - there are a couple of similar modules already on CPAN.

    • interface: Checks that your module supports the methods that a given set of modules support.
    • Object::Interface: Allows the quick declaration of a purely abstract base class, with compile time errors if the sub-class doesn't support the specified errors.

    Interfaces, Interfaces in Perl? and Re: Interfaces seem to cover similar themes too.

    I always saw Java interfaces as a response to the naming resolution problems of C++'s implementation of multiple inheritance. Personally, I think they threw out the baby with the bathwater by only allowing multiple-inheritance of interface :-)

    Compile time warnings are a good thing obviously, but that's a separate issue from interface vs. abstract base classes.

    I have to admit, I still don't have a handle on how what you want is different from Java-ish interfaces, or inheriting from an abstract class. Can you give an example that shows where abstract classes / interfaces wouldn't be enough?

    (If it is different from the Java concept I strongly agree with fever that it should be called something else to avoid confusion :-)

    You said:

    • I'd like to be able to check that an object I receive from somewhere can handle the operations I'm about to perform on it
    • I'd like that check not to dictate the object's implementation of those operations

    Inheriting from an abstract base class seems to meet both of these goals - or am I missing something?

    There could be an argument for having something like this as a way to retrofit an "abstract" interface onto a pre-existing class. I could see that being useful when you don't control the source.

    But if you do control the source expressing the interface using an abstract class (maybe using Attribute::Abstract, Class::Virtual or Class::Virtually::Abstract) would seem more straightforward.

      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.)

        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.

        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?

        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.).

Re: Class::Interface -- isa() Considered Harmful
by BrentDax (Hermit) on Jan 17, 2003 at 08:18 UTC
    I'd personally rather say "Make sure this object can handle any message I can send an Arcade." Since the Airport contains an Arcade, it will just delegate methods like colle­ct_qu­arter­s and play_­annoy­ing_d­ance_­music to the Arcade it contains.
    Why should an Airport act as an Arcade?

    As an example, let's pretend a mall has a skate park in it. I don't think that the mall should act as a skate park in any way--unless you're writing a Tony Hawk game. :^)

    Going back to interfaces, the one thing I see them as being useful for in a language that supports MI is for specifying properties of a class (in the Perl 6 sense of the word). For example, in .NET any serializable class implements the ISerializable interface. (I've probably got some karmic justice coming soon for suggesting that an M$ design is good... ;^) )

    Personally, I would standardize on a 'get_arcade' method for when you're handed something that isn't an Arcade:

    $arcade=$arcade->get_arcade until $arcade->isa('Arcade');

    =cut
    --Brent Dax
    There is no sig.

      It was an unclear example.

      I hesitate to use this example, knowing that the likely response will be "Tying won't work that way in Perl 6." To forestall those replies, I know that this is the case. I'm also not interested in questions of "Why are you doing this anyway?" This is an example of the shortcomings of relying on inheritance. I can come up with clever hacks to get around all this, but I would rather that I not have to do so.

      If you do have a better solution that achives both of my goals (it should be possible to check that things can handle the operations I'm about to perform on them, and this check should not dictate the implementation of these operations), I'm all ears.

        As I pointed out in another node in this node tree, you seem to be looking for:

        use Scalar::Util qw(reftype); sub new { my($class, $args) = @_; return unless $args; return unless reftype($args) eq 'HASH'; # ...

        Also, it looks as if you are trying to blur the line between Perl object interfaces and Perl blessed reference method interfaces. Since the two are fully independent (any Perl blessed reference can be implemented using a reference to any Perl object), I suggest you are asking the wrong question... :-)

        To expand upon my "any Perl blessed reference can be implemented using a reference to any Perl object" - the initial version of my class could use a HASH to store member fields, while a later version uses an ARRAY to improve on efficiency, and reduce storage requirements. In pure OO philosophy, your code should not rely on how my class stores its member fields. If your code is expecting to get a hash reference as an argument, it is my responsibility as the caller to pass you a hash reference. If my hash reference happens to be blessed, it shouldn't matter. You should use reftype() and not care about the blessed'ness of the structure. It is unfortunate that Scalar::Util was not included in earlier versions of Perl.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://227309]
Front-paged by Aristotle
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (6)
As of 2024-04-23 07:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found