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

I'm stretching my very underdeveloped OO muscles and looking for a little guidance.

I've got this perl script which creates a EventRepository object:

#!/usr/bin/perl use strict; use warnings; use EventRepository; my $er = EventRepository->new; $er->gets('http://google.com');

The EventRepository ISA subclass of class FFMech. The EventRepository code looks like this:

package EventRepository; use strict; use warnings; use vars qw(@ISA); @ISA = qw (FFMech); sub new { my $self = {}; bless $self, shift; logi('Creating event repository.'); $self->{_mech} = FFMech->new(); return $self; }

And here is a snippet from the FFMech class, which is basically just a wrapper for WWW::Mechanize::Firefox and has some custom functions that I wrote.

package FFMech; use strict; use warnings; use vars qw(@ISA); @ISA = qw (WWW::Mechanize::Firefox); use X11::GUITest qw/SendKeys FindWindowLike ClickWindow SetEventSendDe +lay/; sub new { my ($class, %args) = @_; my $self = {}; bless $self, shift; $self->_init(@_); return $self; } sub _init { my ($self, %args) = @_; $self->{_mech} = WWW::Mechanize::Firefox->new(launch => 'firefox', a +ctivate => 1 ); $self->{_window_id} = FindWindowLike('Mozilla Firefox'); ClickWindow($self->{_window_id}); if ($self->{_mech}->title =~ /Restore Session/i) { $self->{_mech}->click( {selector => '#errorCancel' } ); } } sub gets { my $self = shift; my ($url, $sec, $syn) = set_args(@_); $self->{_mech}{_mech}->get($url, sychronize => $syn); sleep($sec); }

Though the code works, I can't be doing things properly. In particular, the gets() method in the FFMech class has this line :

$self->{_mech}{_mech}->get($url, sychronize => $syn);

Clearly this is not correct because gets() should not have to know how many levels deep or what methods to call on the object to get to the get() method. I'm missing something fundamental here. Can someone please point me in the right direction? How do I properly call WWW::Mechanize::Fixefox->get() method using the EventRepository object?

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon";
$nysus = $PM . $MCF;
Click here if you love Perl Monks

Replies are listed 'Best First'.
Re: Proper way to call subclass methods?
by choroba (Archbishop) on Mar 08, 2016 at 00:32 UTC
    The mention of @ISA indicates you wanted to use inheritance (which can be nowadays implemented easier with parent), but by wrapping an object in an attribute, you seem to use delegation instead (in a weird way).

    With inheritance, there's no need to wrap the FFMech object in an EventRepository object - each EventRepository object already is a FFMech object, too.

    The gets method seems to belong to EventRepository, not to FFMech: the nested _mech doesn't exist in the former class. With delegation, you'd define a get method in the FFMech class that would call $self->{_mech}->get, and in the EventRepository, you'd do the same: but its get would call FFMech's get, which in turn would call the original 'get'.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      I'm not sure I follow. I removed the EventRepository object out of the code just to try to simplify things:

      my $ffm = FFMech->new; $ffm->gets('http://nyt.com');

      This results in an error:

      Can't use an undefined value as a HASH reference at /usr/local/share/p +erl/5.20.2/WWW/Mechanize/Firefox.pm line 804.

      The gets() method in FFMech looks like this now:

      sub gets { my $self = shift; my ($url, $sec, $syn) = set_args(@_); $self->get($url); sleep($sec); }

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon";
      $nysus = $PM . $MCF;
      Click here if you love Perl Monks

Re: Proper way to call subclass methods?
by Tanktalus (Canon) on Mar 08, 2016 at 16:07 UTC

    I think you have your question backwards. You really want to call superclass methods.

    You're conflating two OO idioms - inheritance (what you're derived from - in perl, we set @ISA, but better is base, and better yet is parent, and, depending on who you ask, there are better options available in Moose, Moo, and others), and composition (also known as "Has-A" - as in, FFMech Has A WWW::Mechanize::Firefox object). Don't do both for the same type.

    You need to pick one. In general, if you want to expose nearly everything, maybe with a few tweaks, inheritance is easier. To set that up, you just use parent 'WWW::Mechanize::Firefox'; and you're mostly done. In your case, I see you have some extra defaults, so you can pass them on in your overridden new:

    sub new { my ($class, %args) = @_; my $self = $class->SUPER::new(launch => 'firefox', activate => 1, %a +rgs); $self->{_window_id} = FindWindowLike('Mozilla Firefox); ClickWindow($self->{_window_id}); if ($self->title =~ /Restore Session/i) { $self->click( { selector => '#errorCancel' } ); } return $self; }
    And you call this as my $ffm = FFMech->new(...);. I leave it as an exercise for the reader to extend this out to the EventRepository subclass - it's far simpler.

    You'll notice that this no longer needs to call $self->{_mech} for getting to the mech object - because $self @ISA mech object, fully initialised!

    For composition, you don't want to set up the @ISA relationship, so you don't set @ISA, call use parent or base or anything like that. Just use WWW::Mechanize::Firefox; to ensure it's loaded, and then you can create it in your constructor. However, then you have to redirect the interfaces you want to expose, e.g.:

    sub get { my $self = shift; $self->{_mech}->get(@_); }
    This can get annoying, so you could generate these functions:
    for my $func (qw(get click ...)) { no strict 'refs'; *$func = sub { my $self = shift; $self->{_mech}->$func(@_); }; }
    You can reduce the scope of that no strict 'refs' bit even more, I'm just being lazy and not testing stuff here, because there's a better way. You remember how I mentioned that there are better options than parent? Moose (and maybe Moo - I haven't used Moo) allows you to say you have a member variable which "handles" certain methods, and Moose will generate the redirection methods for you:
    use Moose; has _mech => ( is => 'rw', handles => [ qw(get click ...) ] );
    Mind you, this will take a bit more work to set up so that you could make that _mech readonly ('ro') and such, and this becomes a dark, deep rabbit hole you can chase forever :) but it will forever transform the way you do OO in perl. Personally, a project has to cross a fuzzy threshold to warrant using Moose, but when it does, then I use it. The downside to this heuristic is that if a project starts out small and then scope creep happens, it can cross that threshold and switching to Moose becomes a bit extra work than if I know it'll be big from the outset and reach for Moose from the beginning.

      OK, I will noodle through this. Thanks. I'm using Daman Conway's old book from 2000 to get up to speed. I guess it's a bit obsolete because it doesn't mention "parent" and "base". Are there any more recent Perl OO books I should be looking at?

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon";
      $nysus = $PM . $MCF;
      Click here if you love Perl Monks

        16 years is a long time in IT/CS. Conway's book is still worth absorbing though. This has some good stuff. I reach for Moo much faster than Moose but opinions differ and some monks think both are overkill/foolish. :P

Re: Proper way to call subclass methods?
by nysus (Parson) on Mar 11, 2016 at 16:45 UTC

    For those interested in this problem, I settled on a solution (at least for now) using Moose. Thanks to everyone who offered suggestions and help. It was invaluable.

    $PM = "Perl Monk's";
    $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon";
    $nysus = $PM . $MCF;
    Click here if you love Perl Monks