Hossein has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I have some classes. Each class is placed in one XXX.pm Each class has a sub new where the content is different from the other classes.

In my code i store all objects (FW.pm) in @fw, and @vlan contains all objects in VLAN.pm and so on ...

What I need to have is only one sub routine that calls one method n each class which displays all objects.

for instance: NT::FW->traverserObjects or NT::VLAN->traverserObjects ...

I'm also new in OO. So if you know a better way of doing it, please let me know.

Thank you :)

Replies are listed 'Best First'.
Re: OO: how to make a generic sub (use roles or inheritance)
by tobyink (Canon) on Jun 27, 2013 at 07:00 UTC

    There are two different ways of doing it. Let's assume you have a Pet::Dog class and a Pet::Cat class, and you want both animals to be able to jump.

    The first technique is to use roles. A role is a unit of functionality that can be imported into several different classes. There are various packages on CPAN for creating roles, but here we'll use Role::Tiny...

    #!/usr/bin/env perl use v5.14; package Roles::Jumper { use Role::Tiny; # The 'jump' method calls 'name', which means that all # classes that consume this role, must provide a 'name' # method. # requires "name"; # And here's the 'jump' method itself. # sub jump { my $self = shift; say $self->name, " jumps!"; } } package Pet::Dog { # The Pet::Dog class should consume Roles::Jumper. # use Role::Tiny::With; with "Roles::Jumper"; sub new { my $class = shift; bless {@_}, $class; } sub name { my $self = shift; return $self->{name}; } sub sound { my $self = shift; say $self->name, " says woof!"; } } package Pet::Cat { # The Pet::Cat class should also consume Roles::Jumper. # use Role::Tiny::With; with "Roles::Jumper"; sub new { my $class = shift; bless {@_}, $class; } sub name { my $self = shift; return $self->{name}; } sub sound { my $self = shift; say $self->name, " says meow!"; } } my $fido = Pet::Dog->new(name => "Fido"); $fido->jump; $fido->sound; my $felix = Pet::Cat->new(name => "Felix"); $felix->jump; $felix->sound;

    The other technique is to use inheritance from a base class. A Pet::Cat is a Pet::Mammal, isn't it? And so is a Pet::Dog. So we create a Pet::Mammal class and put all our common functionality into there. Then Pet::Cat and Pet::Dog can be simple subclasses of Pet::Mammal.

    #!/usr/bin/env perl use v5.14; package Pet::Mammal { sub new { my $class = shift; bless {@_}, $class; } sub name { my $self = shift; return $self->{name}; } sub jump { my $self = shift; say $self->name, " jumps!"; } } package Pet::Dog { use base "Pet::Mammal"; sub sound { my $self = shift; say $self->name, " says woof!"; } } package Pet::Cat { use base "Pet::Mammal"; sub sound { my $self = shift; say $self->name, " says meow!"; } } my $fido = Pet::Dog->new(name => "Fido"); $fido->jump; $fido->sound; my $felix = Pet::Cat->new(name => "Felix"); $felix->jump; $felix->sound;

    So which technique should you use? When there's a clear "is-a" relationship between the classes (a Cat is-a Mammal, a Dog is-a Mammal), and when the base class (Mammal in this case) is potentially useful by itself, then use inheritance. Otherwise, use roles.

    Update: I'll also point out that it often makes sense to use both inheritance and roles. For example a "Guard::Dog" class could inherit from "Pet::Dog" and also compose the "Security::Guard" role.

    package Security::Guard { use Role::Tiny; requires "sound"; sub raise_alarm { my $self = shift; $self->sound; } } package Guard::Dog { use base "Pet::Dog"; use Role::Tiny::With; with "Security::Guard"; }

    Update II: also note that while I've used syntax from Perl 5.14 in the examples above, this is merely for clarity. Role::Tiny and base each support Perl 5.8, and the examples could be rewritten to run on Perl 5.8 with fairly trivial changes.

    package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

      Thanks tobyink for this, I found it very interesting!

      I learned OO on Java, then graduated to C++. So, my first thought on reading the above was: roles here are analogous to Java interfaces — except that a role contains an implementation whereas an interface does not. But then I wondered: how is this different from good old (possibly multiple) inheritance? I changed your first example by removing the references to Role::Tiny and replacing:

      use Role::Tiny::With; with "Roles::Jumper";

      with:

      use base qw( Roles::Jumper );

      in both the Pet::Dog and Pet::Cat classes. The resulting output was the same. So, my first question is: what advantage is there in using Role::Tiny over using simple, single inheritance here?

      My second question arises from this statement:

      When there's a clear "is-a" relationship between the classes..., and when the base class ... is potentially useful by itself, then use inheritance. Otherwise, use roles.

      Can you explain the reasoning here? I’m still trying to get my head around Perl’s approach to OO. In my present state of ignorance, the two techniques you illustrate look like syntactic variations on an identical theme, and I can’t see any reason to prefer one over the other.

      Thanks,

      Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

        "I learned OO on Java, then graduated to C++. So, my first thought on reading the above was: roles here are analogous to Java interfaces — except that a role contains an implementation whereas an interface does not."

        Bingo!

        Or rather, roles may provide implementation. In the Roles::Jumper role, the role specifies that the name and jump methods are part of the interface, but only provides an implementation for jump; it requires classes that consume the role to provide name.

        "But then I wondered: how is this different from good old (possibly multiple) inheritance?"

        Conceptually it's similar, but roles offer some protection against some of the problems associated with multiple inheritance.

        The classic one is when you inherit methods of the same name from multiple parents. With a good implementation of roles (and in this context, Role::Tiny, Moo::Role, Mouse::Role and Moose::Role are all "good") you'll get an exception raised if your role composition causes a method conflict.

        The example in the Moose docs is that role Fragile and role Dancer might both have a break method. If you want to build a class FragileDancer that composes both of those roles, then the way to avoid raising that exception is for FragileDancer itself to provide a break method (which might call Fragile::break or Dancer::break or both), and thus resolve the conflict.

        If Dancer and Fragile had been classes, and FragileDancer multi-inherited from both, then exactly what $fragiledancer->break did would be a bit of a mystery. It would depend on the order of the @ISA array. If you had some code that did:

        $thing->break if $thing->isa("Fragile");

        ... then you might be surprised if $thing suddenly started break-dancing.

        Roles force the FragileDancer class to resolve the conflict in a way that makes sense to that class. It forces the person writing FragileDancer to actually think about the break method; realise that it's a conflict, and realise that it's important to document what it does in FragileDancer because it might differ from the expectations of people familiar with Fragile and Dancer.

        Roles can also be used as a kind of "tagging" for classes. For example, say you've got a bunch of classes which inherit from MyApp::Plugin. Maybe some of the plugins need to be activated at stage 1 of your app, and some need to be activated at stage 2. In that case, you can use empty roles to tag which stage each plugin needs to be activated at.

        package MyApp::Plugin::Stage1 { use Role::Tiny }; package MyApp::Plugin::Stage2 { use Role::Tiny }; package MyApp::Plugin::Frobnicate { use Role::Tiny::With; with "MyApp::Plugin::Stage2"; } ...; foreach my $plugin ($self->plugins) { # The "DOES" method is like "isa", but checks roles # instead of checking inheritance. # if ($plugin->DOES("MyApp::Plugin::Stage2")) { $plugin->activate($self); } }

        Some people have likened the difference between role composition and inheritance to horizontal versus vertical. If you have an inheritance hierarchy, roles are like pulling extra functionality in from the sides.

        "In my present state of ignorance, the two techniques you illustrate look like syntactic variations on an identical theme, and I can’t see any reason to prefer one over the other."

        In the simple pets example, it does make little difference whether you use roles or inheritance. However, in a more complex application where you want a class to be composed of functionality from several different packages, composing multiple roles generally has fewer gotchas than multiple inheritance does.

        Update: I added some more stuff above about FragileDancer.

        package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name

      tobyink++ really useful post. (You should think about putting several of your recent OO-related posts together into YAOO-Tutorial!)

      I think its is worth noting that Roles can be simply implemented using basic Perl5 mechanisms (and with less loadtime/runtime costs):

      #! perl-slw use strict; use 5.010; { package Roles::Jumper; ##requires "name"; sub import { my $caller = caller; { no strict; die "'name' method required" unless exists ${ "$caller\:\: +"}{name}; *{ "$caller\:\:jump" } = \&jump; } return; } sub jump { my $self = shift; say $self->name, " jumps!"; } } { package Pet::Dog; ##use Role::Tiny::With; ##with "Roles::Jumper"; #require Roles::Jumper; ## used if package in separate file Roles::Jumper->import; sub new { my $class = shift; bless {@_}, $class; } sub name { my $self = shift; return $self->{name}; } sub sound { my $self = shift; say $self->name, " says woof!"; } } { package Pet::Cat; ##use Role::Tiny::With; ##with "Roles::Jumper"; #require Roles::Jumper; ## used if package in separate file Roles::Jumper->import; sub new { my $class = shift; bless {@_}, $class; } sub name { my $self = shift; return $self->{name}; } sub sound { my $self = shift; say $self->name, " says meow!"; } } my $fido = Pet::Dog->new(name => "Fido"); $fido->jump; $fido->sound; my $felix = Pet::Cat->new(name => "Felix"); $felix->jump; $felix->sound; __END__ C:\test>roles.pl Fido jumps! Fido says woof! Felix jumps! Felix says meow!

      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

        "I think its is worth noting that Roles can be simply implemented using basic Perl5 mechanisms"

        Ultimately all of Moose can be implemented using basic Perl5 mechanisms. (Because all of Moose is implemented using basic Perl5 mechanisms!)

        Yes, this aspect of roles is basically just importing. But Role::Tiny also gives you method conflict checking out of the box (i.e. if you compose two roles with the same method name into the same class, you don't get a random choice, you get an error). And it provides nice integration with Class::Method::Modifiers if you happen to have it installed.

        package Cow { use Moo; has name => (is => 'lazy', default => sub { 'Mooington' }) } say Cow->new->name
      Thank you :)

      I learned a lot reading your answers :) and still learning ;)

      Best regards

      Hossein