Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

Inheritance confusion

by the_slycer (Chaplain)
on May 20, 2005 at 11:46 UTC ( #459039=perlquestion: print w/replies, xml ) Need Help??

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

Hi monks, we've recently run into a problem in our testing environment.. to make a long story shorter, we have a whack of crons that run. We have finally setup a test cron server. Problem is that half of the crons are FTP'ing data somewhere. This brings up a big problem, in that we can't properly test the crons because we're ftp'ing data to "live" servers.

So, one of the ideas that we've kicked around, and seems to be a simple solution, is to build a wrapper/proxy class around the Net::FTP module, and on connection (and login), overwrite the passed options with our test ftp server information.

The problem is, I can't seem to get this to work quite right, here's what I've got so far:
package Custom::FTP; use Net::FTP; @ISA = ('Net::FTP'); #proxy module for Net::FTP - allows us to change hostname etc for test +ing purposes sub new { my $class = shift; my $self = {}; bless $self, $class; my $remote_host = shift; #check to see where we are if ( _not_prod() ) { $remote_host = 'our.test.host'; } $self->{parent} = Net::FTP->new($remote_host, @_); return $self; } sub login { my $self = shift; my @opts = @_; if ( _not_prod() ) { $opts[0] = 'testuser'; $opts[1] = 'testpassword'; } my $parent = $self->{parent}; return $parent->login(@opts); } sub _not_prod { #more code }
Which seems to work fine, however, calling a $ftp->ls() on the returned object does not quite do what I need, I get an error stating Not a GLOB reference at /usr/lib/perl5/site_perl/5.6.0/Net/FTP.pm line 905. Looking into the Net::FTP module, I can see why this is happening, the module is being called with a class of Custom::FTP rather than Net::FTP, which means that the variable passed in as the "object" is the Custom::FTP object rather than the parent object.. and the GLOB on line 905 is accessing an undef'd value.

So, one way around this is to write a custom ls subroutine in the Custom::FTP module, but this seems to be "wrong" in that it's not inheritance of any kind, rather it's overriding every routine in the Net::FTP module to simply call the parent class's routine..

Is there some other way to handle this? Am I not inheriting correctly? And before anybody makes the suggestion, I'd love to rewrite all our crap to include proper test flags etc, but the lack of time to do so is a big factor in finding another way..

Replies are listed 'Best First'.
Re: Inheritance confusion
by BrowserUk (Pope) on May 20, 2005 at 12:23 UTC

    The problem arises because Perl's inheritance is based upon blessed hashes, but Net::FTP uses blessed GLOB's ( courtesy of IO::Socket::INET).

    One way around this would be to use an AUTOLOAD sub to extract the Net::FTP glob from your hash and re-dispatch the missing method via that. Something like (untested):

    sub AUTOLOAD { my $self = shift; my $super = $self->{parent}; ( my $method = $AUTOLOAD ) =~ s[.*::][]; return $super->$method( @_ ); }

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.
      The problem arises because Perl's inheritance is based upon blessed hashes...

      Perl can use all kinds of references as the underlying data structures for objects. You don't have to use hashrefs, it's just that they're the most common, since object data often wants to be stored according to name.

      You can see Regexp refs and SCALAR refs being used as objects, in TheDamian's excellent Object Oriented Perl.

Re: Inheritance confusion
by holli (Abbot) on May 20, 2005 at 12:09 UTC
    Do you have your usernames/passwords/server addresses hardcoded in the scripts? If so, I'd put them into a config file. So you can simply have one config file for production use and one for your tests.


    holli, /regexed monk/
Re: Inheritance confusion
by Fletch (Chancellor) on May 20, 2005 at 13:09 UTC
    So, one way around this is to write a custom ls subroutine in the Custom::FTP module, but this seems to be "wrong" in that it's not inheritance of any kind, rather it's overriding every routine in the Net::FTP module to simply call the parent class's routine..

    This isn't necessarily a bad approach, and in fact it's common enough to have a name: MockObject (see that link for references, or try googling "mock object testing").

Re: Inheritance confusion
by chromatic (Archbishop) on May 20, 2005 at 15:29 UTC

    This sounds like a case for composition and delegation instead of inheritance.

    Create a Net::FTP object in your constructor, but don't inherit from Net::FTP. Add an AUTOLOAD that dispatches all appropriate calls to the object. Something like this untested code would work:

    sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my ($meth_name) = $AUTOLOAD =~ /::(\w+)$/; my $method = $self->{_ftp}->can( $meth_name ); return $self->{_ftp}->$method( @_ ) if $method; # handle error here }
Re: Inheritance confusion
by splinky (Hermit) on May 20, 2005 at 15:01 UTC
    I think if you look at the code for Net::FTP, you'll find your answer, as Net::FTP inherits from IO::Socket::INET. As another poster mentioned, you're implementing your subclass as a hash, whereas Net::FTP is implemented as a typeglob. That's what's causing the problem. One of the weaknesses of Perl's OO implementation is that you have to know a class's internal representation in order to inherit from it.

    Anyway, look in the code for Net::FTP and see how its constructor works, and implement yours the same way.

      One of the weaknesses of Perl's OO implementation is that you have to know a class's internal representation in order to inherit from it.

      Actually, this is not strictly true. He could use an inside-out-object-like design, and avoid using the object reference to store the data at all. He could initialize the object using the original constructor, and rebless it into his package. Voila, inheritance without knowing about the parent's internal representation. By the way, I am not saying this is the best design in these circumstances, only that it is possible.

Re: Inheritance confusion
by cowboy (Friar) on May 20, 2005 at 18:34 UTC
    Why do you need to override the new method? Maybe this would work:
    package Custom:FTP; use base qw( Net::FTP ); sub login { my $self = shift; my @opts = @_; if (_not_prod()) { $opts[0] = 'testuser'; $opts[1] = 'testpassword'; } return $self->SUPER::login(@opts); } sub _not_prod { # check production }
    I also agree with the poster above who mentioned using a config file. That seems the safer place to check if it's production, and just return different server/user/pass values if it is the devel server.
      We need to modify the *server* it's connecting to as well, which is done in the constructor.
      I'd love to externalize the config, that has it's own issues as well (mainly time related)
Re: Inheritance confusion
by Anonymous Monk on May 20, 2005 at 16:39 UTC
    Better yet in new do something like
    my $self=Net::FTP->new(@_); bless $self,shift; local *r=*$self; %r=@_; $r{mystuff}=...; return$self;
    Then in any method do
    my $parent=shift; my $self=do{local *self=*$parent;\%self}
Re: Inheritance confusion
by Thelonious (Scribe) on May 23, 2005 at 09:07 UTC
    Just inherit properly, using the glob as the underlying data structure. The only thing you'll have to pay attention to is how you access and set your data:

    package My::FTP; use base 'Net::FTP'; sub new { # my($class,$host,%args) = @_; my $self = Net::FTP::new(@_) or die $@; ${*$self}{_custom_data} = 'CUSTOM DATA'; return $self; } sub custom { my $self = shift; return ${*$self}{_custom_data},"\n"; } package main; my $ftp = My::FTP->new(shift); $ftp->login(shift,shift) or die $ftp->message; print $ftp->pwd,$/; print $ftp->custom,$/; print ref $ftp,$/; __END__ / CUSTOM DATA My::FTP

    Hope this helps.
      try $ftp->{parent}->ls() instead of $ftp->ls()
      that should work, right?

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://459039]
Approved by ysth
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (3)
As of 2020-06-02 02:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Do you really want to know if there is extraterrestrial life?



    Results (12 votes). Check out past polls.

    Notices?