Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Object Terminology

by stvn (Monsignor)
on Jan 11, 2004 at 19:22 UTC ( #320507=perlmeditation: print w/replies, xml ) Need Help??

There is a good amount of discussion about OOP on this site. One of the major sticking points of OO I think is the plethora of terminology out there, and the lack of simple and clear explanations of that terminology. This meditation is my attempt at doing just that in regards to Object Composition.

I think this is an important thing for this site, and Perl in general. Given the direction of Perl 6 and its emphasis on improving Perl's OO features, along with things like Traits/Roles and other such esoteria starting to show up on the Perl 6 language list, its only gonna get worse around here.

Disclaimer: This is the first draft, and by no stretch of the imagination is it meant to be the final draft. But in the interest of not creating things in a vacuum, I am releasing it for comment by the monks. Remember, the whole point is to keep it as simple and concise as possible.


Object Composition

There are 2 basic methods to object composition; inheritance and delegation. I will describe different approaches to both, as well as explain where I see Interfaces and Traits/Roles as fitting into all this.

Inheritance

Single Inheritance

This is the most common kind of inheritance. It is your basic "is-a" relationship. A class can inherit the methods and attributes of another class. Usually the derived class is a specialization of the base (parent) class.

A Manager is-a type of Employee which is-a type of Person.

Multiple Inheritance

This is less common and greatly misunderstood and under-utilized form of inheritance (IMHO). Its the same "is-a" relationship as Single Inheritance, but with the possibility of 2 or more parent classes.

A Platypus is-a Mammal but it also lays eggs like a Bird.

Mix-in inheritance

Mix-ins are partial classes. They are neither multiple inheritance (although they can be multiply inherited) or Interfaces (they do not define the "is-a-type-of" relationship (see below)), but instead they are a way to alter a specific classes behavior. The best example i can think of is from Python and its web server class.

The regular web server serves each request sequentially. But there are 2 mix-ins provided. One to fork for each request, another to spawn a new thread for each request. The mix-ins only override the bare minimum of methods needed to change the class in the way they want to.

One drawback of mix-ins is that they tend to play havoc with inheritance. Just because your parent class accepted a mix-in does not mean your derived class necessarily can. It end up requiring alot of internal knowledge of both the base class, the derived class and the mix in. Also, to really make effective use of mix-ins you must engineer your classes to be mix-in-able, which can at times get really messy.

Delegation

Delegation is where an object itself contains other objects which it then delegates responsibilities too. This is sometimes characterized as a "has-a" relationship.

The goal is not to mimic inheritance through delegation, but to encourage the encapsultation of multiple classes into the behavior of a single class.

A Car has-a an engine, is has-a set of wheels and has-a climate control system.

This is by far the simplest form of object composition, but by no means the easiest. It is the prefered method of alot of OO Gurus, and C++ programmers in particular.

Other

Interfaces

Interfaces are not really inheritance per-say. A class that implements an Interfaces enters into a contract with all consumers of that class that the methods (implemented or stubs) from said interfaces will be there. Interfaces are best characterized as "is-a-type-of". Interfaces facilitate Interface Polymorphism, which means you can treat an object in a polymorphic manner based upon the interfaces it implements rather than the class it is. This is best shown in an example:

my ($list) = @_; my @clones; # check that $list implements # the Iterable interface if ($list->isa("Iterable")) { # the Iterable interface implements # the iterator method, so we call it my $iterator = $list->iterator(); # now make sure that the $iterator # returned actually implements the # Iterator interface ($iterator->isa("Iterator")) || die "bad object interface : no +t Iterator"; # now that we know we have a proper # Iterator, we can be assured it will # implement the hasNext and next methods # so we can use them with confidence in # what they will do. while ($iterator->hasNext()) { my $item = $iterator->next(); # now after getting the $item from the # Iterator, we want to clone it (if possible) # and so we first check to see if the $item # implements the Cloneable interface, which # tells us that the object will clone itself # when the method clone is called push @clones, $item->clone() if $item->isa("Cloneable"); } } # at this point @clones is filled with all # the items in the given list that are able # to be properly cloned
Interfaces have been made popular by Java. In Java, Interfaces do not have an implemetation, and instead are just markers to indicate that a class will implement a set of methods. This is not a rule for Interfaces, instead it is a rule for Java.

Traits/Roles

Traits are a concept which is being discussed for inclusion into the Perl 6 object model. As i understand traits, they are like mix-ins where the trait itself is not a complete class, and only a piece of it. They are also like interfaces in that an object which has a certain trait is expected to respond to the methods of that trait.

Traits are meant to be incomplete as a class, but yet complete as a concept. This avoids the need to understand the class internals that usually comes with mix-ins. It very nicely maps to the ideas of traits in the real world, where a trait is just one facet of a person's personality, or aspect of their physical being. To use our single inheritance model:

A Employee is-a Person.
A Manager is an Employee who has managerial traits.
Of course, this example is in a perfect world, we all know that just cause your a manager doesn't mean you know how to manage anything at all :)

Traits do not have internal state and therefore do not force any internal fields upon the object. They can however insist the object implement a certain set of methods.

Replies are listed 'Best First'.
Re: Object Terminology (and an awful shaggy dog story)
by Ovid (Cardinal) on Jan 11, 2004 at 20:52 UTC

    (Note: if you've not heard the term "shaggy dog story", here's a short description -- and I don't claim that mine is even remotely humorous)

    Nice work. ++. And here's some food for thought for programmers on some of the strength and limitations of those features that are built into Perl (thus leaving off discussion of mixins and traits).

    Single inheritance avoids problems with confusing inheritance issues (e.g., which class did that method come from?) and some obscure composition issues which are detailed more carefully in the original traits paper. It's simple to use, but it leads to problems with code duplication. For example, you have a Mammal class and you want to instantiate a Tiger, which just happens to be a subclass from Mammal. All is well and good. However, you're a deviant terrorist version of Siegfried and Roy and you want to create an exploding tiger. In this case, you have a Bomb class and you think your Tiger isa Bomb in addition to a Tiger. Unfortunately, you programming language doesn't support multiple inheritance so you just cut and paste the code. Now you have code duplication. Bad.

    One method that Java uses to get around this is the interface. If a class implements an interface, then that class must implement the methods described in that interface. You could create a Bomb interface and that would ensure that all classes that implement bomb would have an explode method, but that wouldn't solve the code reuse problem.

    With multiple inheritance, like we have in Perl, you can state this:

    package Tiger; use base qw(Mammal Bomb);

    Now let's say that mammal inherits from LifeForm, so your class heirarchy looks like this:

               LifeForm
                   |
                Mammal  Bomb
                   \     /
                    Tiger

    This looks fine, but what you, the deviant Siegfried and Roy didn't know, is that the creator of LifeForm took into account stories of spontaneous human combustion and build an explode method directly into the LifeForm class. What happens? When you call $tiger->explode, &Tiger::explode is not found, but this is OO programming! The @ISA tree is searched leftmost, depth first. Thus, Perl looks for &Mammal::explode and doesn't find it. Then it looks for &LifeForm::explode, finds it and calls it. Bomb::explode is never called. Unfortunately for you, this is a rather weak explosion that simply consumes your Tiger in fire. Most disappointing.

    You can get around this by rearranging the order of classes in your use base ... statement, but what if Bomb now implements a method that you need from Mammal? You have the same problem. Yet another way of dealing with this problem is to have this method:

    sub Tiger::explode { my $self = shift; $self->Bomb::explode; }

    That works, but now we've encoded implementation information directly into our class and made it less flexible. This directly contravenes some of the benefits that we expect from OO programming.

    Delegation, on the other hand, neatly avoids all of these problems. In your Tiger constructor, let's assume that you have a _bomb slot. This slot will contain a bomb object. Later, when you wish to explode, you do this:

    sub new { my ($class, $data) = @_; # deliberately simplistic! $data->{_bomb} = Bomb->new; bless $data, $class; } sub Tiger::explode { my $self = shift; $self->{_bomb}->explode; }

    With this, we explicitly call the method we want, as in the $self->Bomb::explode call above, but we limit the knowledge of which class we are using in one spot (in this case, in the constructor).

    On the surface, this might not seem like much of a win, but what happens if this is more complicated and we have four methods we need from our Bomb class?

    sub set_timer { my $self = shift; $self->Bomb::set_timer; } sub light_fuse { my $self = shift; $self->Bomb::light_fuse; } sub explode { my $self = shift; $self->Bomb::explode; } sub disarm { my $self = shift; $self->Bomb::disarm; }

    Now we have a problem. Without delegation, if we gained a conscience and wanted to change the Bomb to a Toy::Bomb, we have multiple places that we would need to change the class name. With delegation, we only have one place to change it and the delegated methods don't change.

    Another benefit of delegation is that you are relying only on the published implementation. With inheritance, you must comply explicitly with the inherited implementation and (particularly with Perl), if private methods change, you might accidentally step on them. You are also more likely to be forced to use the parent class implementation. If the parent class uses a blessed hashref, you will use a blessed hashref whether you want to or not. Delegation decouples the implementation from the Bomb and Mammal implementation, with the exception of the implied contract that "this interface will not change". The major drawback of delegation, though, is that it can often take more work to set up. You can't inherit the methods that you want, so you have to explicitly create them.

    As any competent manager will tell you, delegation is the preferred method for dealing with exploding tigers.

    Cheers,
    Ovid

    New address of my CGI Course.

      Ovid,

      Exploding Tigers. Nice, although you might get some slack from PETA for it. :)

      While I agree with all your points (for the most part). I still love my multiple-inheritance. Eiffel provides a handy way to handle some of those name conflicts you speak of, by allowing you to rename methods (among other things). Now Eiffel forces you to adjust your methods to the renamed method. In perl though, this is not nessecary. I submit the following code:

      # assume that these 2 methods are # defined in some module and then # exported into your classes's # package namespace. sub redefines ($$$) { no strict 'refs'; no warnings 'redefine'; my ($package, $old_name, $new_name) = @_; my ($caller_package) = caller(); $caller_package->isa($package) || die "Can't rename from a package you do not inherit from"; my $old_method = $package->can($old_name); (defined($old_method)) || die "you cannot rename a method you dont have"; my $current_method; if (exists ${"${caller_package}::"}{$old_name}) { $current_method = \&{"${caller_package}::$old_name"}; } else { $current_method = sub { die "Method no implemented" } } *{"${caller_package}::$old_name"} = sub { my $self = shift; my ($_caller_package) = caller(); return $self->$new_name(@_) if ($package->isa($_caller_package)); # or $self->$current_method(@_); }; *{"${caller_package}::$new_name"} = $old_method; } sub next_method { my ($self, $package, @args) = @_; my ($p, $f, $l, $function) = caller(1); $self->isa($package) || die "Can only dispatch on ancestors of $p"; my @module_path = split /\:\:/, $function; my $caller_function = $module_path[-1]; my $dispatch = $package->can($caller_function); return $self->$dispatch(@args); }
      Your Exploding Tiger can then be implemented as such.
      use strict; use warnings; package ExplodingTiger; ## import the 2 functions above somehow @ExplodingTiger::ISA = qw(Tiger Bomb); redefines("Tiger", "explode", "spontaneouslyCombust"); sub new { my ($class) = @_; my $exploding_tiger = { bomb_type => "Bomb" }; return bless($exploding_tiger, ref($class) || $class); } sub explode { my ($self, @args) = @_; print "ROAR!!! I'm an exploding Tiger!\n"; $self->next_method($self->{bomb_type}, @args); } 1;

      See my scratchpad for a complete runnable example.

      Your delegation solution neglected to allow for the original spontaneous combustion behavior to still remain around. It gets redefined in ExplodingTiger::explode. Also, you are faced with the problem that if code from your inherited Tiger class uses the explode method anywhere for a specific purpose it will dispatch to the ExplodingTiger::explode method instead, and the behavior of the Bomb::explode method it delegates too may not be appropriate. The above solution will account for that (see the scratchpad example for the details). It also allows for you to inherit other Bomb methods without needing to delegate.

      Another benefit of delegation is that you are relying only on the published implementation. With inheritance, you must comply explicitly with the inherited implementation ...
      I am not 100% sure i understand what you are saying here. I assume you are saying that you must deal with the fact a class is inherited, and that is part of the implementation? If that is not what you are saying then disregard the rant that follows, if it is what you are saying, then ...

      I believe this is not an issue of inheritance as a concept, but in the implementation of various documentation tools (javadoc, I curse thee!!!).

      In Eiffel, there are 2 "views" of a class available in the documentation. A view of the methods implemented by the class directly, and a "flattened" view of all the methods available from the class (all implemented and all inherited).

      I believe this takes the OO idea of encapsulation to its next level. In encapsulating inheritance information as well. I mean after all you are supposed to encapsulate/hide the implementation in OOP, and is not inheritance part of a classes implementation?

      -stvn
Re: Object Terminology
by gmax (Abbot) on Jan 11, 2004 at 20:37 UTC

    Nice introduction. I would like to offer a few comments.

    One concept that you haven't touched and should deserve some explanation is Encapsulation, which in Perl has a few peculiar aspects.

    Encapsulation is restricting data into an object container, with the purpose of protecting data against accidental manipulation and reducing the program complexity. One basic idea of OOP is to allow (or at least to recommend) data access through class methods only.

    Perl encourages encapsulation "by good manners", as Damian Conway puts it, even though it allows stronger encapsulation through some tricks.

    Also, Polymorphism should be explained a bit more. Perl allows polymorphism with and without inheritance. For example, you can do something like the following even with a non-OOP language. (Actually this is what I used to do - using C - in the late 1980s, when I learned OOP but C++ compilers weren't easy to get.)

    #!/usr/bin/perl -w use strict; # polymorphism without inheritance my @objects = ( { name => 'sheep', speak => sub { print "baaah"; } }, { name => 'dog', speak => sub {print "woof";} }, { name => 'mouse', speak => sub {print "squeak (almost silently)" ;} }, { name => 'fish', speak => sub { print ".oO()" } } ); for (@objects) { print "a $_->{name} goes "; &{$_->{speak}}; print "\n" } __END__ a sheep goes baaah a dog goes woof a mouse goes squeak (almost silently) a fish goes .oO()

    Polymorphism through inheritance is what is more commonly accepted as part of the OOP paradigm and it is the basis for object reuse. (At least in theory. Things in the real world could take different shapes).

     _  _ _  _  
    (_|| | |(_|><
     _|   
    
      Excellent point. Both of those terms deserve a space in this document. As I said, I am continually editing the original, as its just the first draft. I will add this in there ASAP.

      I know this was just meant as a quick example for demonstration purposes, but you'd think you would want a return value from the subs, rather than a printed value. This allows a greater functionality of that method, such as placing the value in a scalar rather than printing it to the screen immediately. Meaning:

      #!/usr/bin/perl -w use strict; my @objects = ( { name => 'sheep', speak => sub { "baaah" } }, { name => 'dog', speak => sub { "woof" } }, { name => 'mouse', speak => sub { "squeak (almost silently)" } }, { name => 'fish', speak => sub { ".oO()" } } ); printf "a %s goes %s.\n", $_->{name}, $_->{speak}->() for @objects; __END__ a sheep goes baaah. a dog goes woof. a mouse goes squeak (almost silently). a fish goes .oO().

      (This is not even nitpicking or saying you did it a dumb way. I was just proud of my own observation on how to improve such a thing for wider use :P)

Re: Object Terminology
by chromatic (Archbishop) on Jan 11, 2004 at 21:57 UTC

    I think explaining delegation, interfaces, and especially Traits and roles in terms of inheritance is bound to confuse people.

    There are two questions that any of these schemes has to answer:

    • What can this object do? (Polymorphism -- can I treat it like something I already know about even if it is different?)
    • How can I share code? (Code reuse)

    Inheritance is one way to answer both questions. Composition and delegation are others. There is some overlap, but there doesn't have to be. Polymorphism is much, much more important than inheritance.

      I think you may have mis-read my section groupings. If you re-read the document, I tried to make Inheritance one section, and then Delegation another section, then finally Interfaces and Traits under the Other section. I am not trying to describe everything in terms of inheritance.

      Polymorphism is much, much more important than inheritance.
      I couldn't agree more (hence the only code example was regarding Interface Polymorphism). I am going to add a section on Polymorphism and Encapsulation per gmax's suggestion above.

      -stvn
        I am not trying to describe everything in terms of inheritance.

        That may not be your aim, but that's precisely what your text does:

        • The goal is not to mimic inheritance through delegation...
        • Interfaces are not really inheritance per-say.
        • (Regarding Traits and Roles) To use our single inheritance model:

          A Employee is-a Person.
          A Manager is an Employee who has managerial traits.

        Framing the entire discussion in terms of inheritance has the unfortunate tendency of emphasizing the second of my two questions above at the expense of the (more important) first question.

        I'd much prefer discussing each of these techniques in relation to those questions.

Re: Object Terminology
by EdwardG (Vicar) on Jan 12, 2004 at 10:28 UTC

    Nice start stvn!

    Here are my observations and questions about this draft, since you asked :)

    General Comments

    • Who is your audience, and what is their assumed level of knowledge and experience?

    • It might be useful to have some introductory text that gives your subject a context and establishes some clearer boundaries

    • There are many other sources of introductory information available on the WWW, perhaps you could link to, or quote from some of these to a) provide a foundation and context for your writing and b) provide further reading where your treatment is deliberately light and c) support your definitions

    • When you make assertions such as
      greatly misunderstood
      it would help the reader if you also give an example of how the subject is misunderstood, otherwise how is the reader to know what the wrong understanding actually is, so they can avoid it themselves

    Specific Comments

    • You explain the IS-A and HAS-A relationships, but in your example of multiple inheritance you also mention a LIKE-A relationship:
      A Platypus is-a Mammal but it also lays eggs like a Bird.
      This could be confusing since LIKE-A has no further explanation or comparison in your text.

    • Is it really necessary to try and explain mix-ins? Frankly I've not heard of them before (not that I'm a good yardstick), but it sounds like they are an unwelcome hybrid based to some extent on an abuse of language features. If it is true that they do
      tend to play havoc with inheritance
      then perhaps a lesser discussion would be appropriate, maybe just a single sentence for the sake of completeness.

    • I found this sentence unclear:
      A class that implements an Interfaces enters into a contract with all consumers of that class that the methods (implemented or stubs) from said interfaces will be there.
      Will be where? I think I know what you mean. Perhaps this could be re-worded like this:
      A class that implements an Interface enters into a contract with all consumers of that class that it will implement all methods declared by the Interface.

    • I also found your introduction of the IS-A-TYPE-OF relationship somewhat confusing.
      Interfaces are best characterized as "is-a-type-of"
      To me this reads as though "Interface" is synonymous with "Inheritance". In contrast, I conceptualise Interfaces as a HAS-A relationship but where the details are left to the consumer of the object. To me an Interface says things like
      A car has a dashboard that shows current speed and distance travelled, but it doesn't matter (to me, the base class) what this dashboard looks like or even how it communicates these details.

    Please don't take any of this as negative criticism. As I said, I think what you've written so far is a good start.

    __________________
    Update: Added bullets for clarity

      Edward

      Please don't take any of this as negative criticism.
      Not at all. Your comments are exactly what i am looking for!

      I can see now that comments like "greatly misunderstood" are really too much my opinion, and probably need to either edited out or explained better. I was initially not going to provide any links, since i felt it might muddy up the simplicity, but you make a good point, so maybe I will provide a "further reading" type section.

      Anybody got any good OO links they want to suggest, I am all ears. (I've got my own, but I dont know them all)
      Your specific comments in particular are right on. I will take them all under advisement as i do the first re-write.

      Thanks again, excellent comments.

      -stvn
        Here's a start: As a side note, I would emphasize that interfaces in Java also represent a sort of inherent 'contract', basically saying, "All objects of this type will have these methods, and those methods should do this". They impose a type constraint over the object, which in turn allows for polymorphism without inheritance, which in turn is a good thing. Inheritance breaks encapsulation by giving you intimate knowledge and access to your super classes' wibbly bits. The bottom line is you should usually give some thought to whether or not a class is going to be inherited from, and what will happen if it is inherited from. Otherwise, overriding certain behavior and not others can break code in unpredictable ways (this has bitten me on the ass enough times to make me worry about it).

        Of course, there are many times when inheritance is useful, and I certainly do use it a lot. Superclasses are typically very bare abstract classes with only common behavior, however, and are not typically instantiated by themselves.

        But that's just like, my opinion, man :).
        Allen

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://320507]
Approved by gmax
Front-paged by kutsu
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (6)
As of 2018-07-16 07:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    It has been suggested to rename Perl 6 in order to boost its marketing potential. Which name would you prefer?















    Results (333 votes). Check out past polls.

    Notices?