What is the preferred convention for Perl Inheritance - perl

In the example below, I have 3 different syntax/mechanisms for defining inheritance. All of them work. Can someone tell me which one is preferred and why (yes, I know "there is more than ..."). Also, why do I need "use WB" in 1 case and not the other 2. TIA
The main -
#!/usr/local/bin/perl -w
#######################
use strict;
use River;
use Ocean;
use Lake;
my $pName = $0; $pName =~ s/.*\///; $pName =~ s/\.\w+$//;
my #sources = (Ocean->new ('Pacific', 1),
River->new ('Brazos', 0),
Lake->new ('Tahoe', 0) );
foreach (#sources) {
$_->printIfSaline ($pName);
}
The modules (Lake, River and Ocean inherit from WaterBody):
######################
# File: Lake.pm
######################
package Lake;
use strict;
use WaterBody;
our #ISA = ('WaterBody');
sub new {
my $class = shift;
$class->SUPER::new(#_)
}
sub printIfSaline {
my ($self, $prompt) = #_;
my $name = $self->getName();
my $taste = $self->isSaline() ? "salty" : "sweet";
print "$prompt: Lake $name has $taste water\n";
}
1
######################
# File: Ocean.pm
######################
package Ocean;
use strict;
use parent 'WaterBody';
sub new {
my $class = shift;
$class->SUPER::new(#_);
}
sub printIfSaline {
my ($self, $prompt) = #_;
my $name = $self->getName;
my $taste = $self->SUPER::isSaline() ? "salty" : "sweet";
print "$prompt: $name Ocean has $taste water\n";
}
1
######################
# File: River.pm
######################
package River;
use strict;
use base 'WaterBody';
sub new {
my $class = shift;
$class->SUPER::new(#_);
}
sub printIfSaline {
my ($self, $prompt) = #_;
my $name = $self->getName;
my $taste = $self->isSaline ? "salty" : "sweet";
print "$prompt: $name river has $taste water\n";
}
1
######################
# File: WaterBody.pm
######################
package WaterBody;
sub new {
my $class = shift;
my $self = {};
$self->{name} = shift;
$self->{saline} = shift;
bless $self, $class;
return $self;
}
sub getName {
my ($self) = #_;
$self->{name}
}
sub isSaline {
my ($self) = #_;
$self->{saline}
}
1

The use parent pragma sets up the #ISA at compile time. From parent
Establish an ISA relationship with base classes at compile time
When you use ParentClass; and then manually set up #ISA that happens at run time. In this case code in BEGIN, CHECK, or INIT blocks won't be able to use the inheritance hierarchy without extra work.
The use base pragma is older and parent is recommended in docs. From base
Unless you are using the fields pragma, consider this module discouraged in favor of the lighter-weight parent
Thus I'd say go with use parent.

Manipulating #ISA is the oldest way. base was the second way, and parent is even newer. So, I'd recommend parent for new projects.

use parent 'Foo::Bar';
is cleanest, although the repetitious
use Foo::Bar qw( );
our #ISA = 'Foo::Bar';
is still commonly used. The former also has the advantage of happening sooner (when the file is compiled) than the latter (when the file is executed), though that rarely matters.
base is discouraged because it silences errors that occur when a module is loaded.

Related

Perl - Can't locate object method via "Module::SUPER"

This is my first time using OOP with perl. I am in the processes of refactoring a 4k line procedural program at work. It seems pretty straight forward but I am having an issue with inheritance and SUPER.
Error message:
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
I have tried, use base, parent and setting #ISA but they all get the same error. I'm sure I have to be overlooking something.
(This is not code from the program I am working on. Just an example that produces the same error)
All .pm and .pl files are in the same directory in this example. In the program I am working on the main program is in bin and the modules will be in ../modules(relative to bin).
I would assume this would be all I need to make that work:
use lib "../modules";
If I am wrong in thinking that please let me know.
Parent Module
package BaseModule;
use strict;
use warnings;
sub new {
my $class = shift;
my $self = {
ARRAY => shift,
DIVIDER => ","
};
bless ($self, $class);
return $self;
}
sub array {
my $self = shift;
if(#_) { $self->{ARRAY} = shift };
return $self->{ARRAY};
}
sub divider {
my $self = shift;
if(#_) { $self->{DIVIDER} = shift };
return $self->{DIVIDER};
}
sub testSub {
my $self = shift;
print join($self->{DIVIDER}, #{ $self->{ARRAY} } );
return 1;
}
1;
Child Module
package Module;
use strict;
use warnings;
#use base qw(BaseModule);
#require BaseModule;
#our #ISA = qw(BaseModule);
use parent qw(BaseModule);
sub new {
my $class = shift;
my $self = $class->SUPER::New(#_);
$self->{STRING} = shift;
bless ($self, $class);
return $self;
}
sub string {
my $self = shift;
if(#_) { $self->{STRING} = shift };
return $self->{STRING};
}
sub testSub {
my $self = shift;
print "$self->{STRING}:\n";
$self->SUPER::testSub();
return 1;
}
1;
Do I need to bless the child class if the parent class returns an already blessed $self?
Main Script
#!/usr/bin/perl
use strict;
use warnings;
use Module;
my $module = Module->new([1, 2, 3, 4, 5], "Example");
$module->divider(" | "); # Test Changing divider;
$module->testSub();
Any help is greatly appreciated.
"Can't locate object method "New" via package "Module::SUPER" at Module.pm line 10"
You try to call BaseModule::New whis hasn't been defined (did you mean BaseModule::new? Perl is case sensitive).
Do I need to bless the child class if the parent class returns an
already blessed $self?
No, $self at that point is already blesses (you could check that by means of Scalar::Util::blessed().

Can't locate object method XX via package "1"

I am well aware that there are several questions on a similar subjects but I fail to see how to apply the answers to my problem :
< Can't locate object method "idx" via package "1" >
What I don't understand is that I am using the same architecture in two packages and that it is OK in the first one... Where is the package "1" coming from ?
Here is the package that works fine :
package ObjA;
use warnings;
use strict;
use Data::Dumper;
use Carp;
use ObjB;
#CONSTRUCTOR AND INITIALISATION
sub new {
my $class = shift;
my $self = {#_};
bless($self,$class);
$self->language();
return $self;
}
sub load {
my $self = shift;
open (my $stream,"<",$self ->{name});
my #glob_xs=();
my $i = 0;
while (<$stream>){
$i += 1;
my #x = extract($stream,());
#glob_xs=(#glob_tokens,#x);
}
$self->tokens(\#glob_xs);
}
sub extract{
my ($stream,#x) = #_;
my $line = <$stream>;
chomp $line;
if ($line =~ /^\s*$/){
return #x;
}
print join("/",split("\t",$line));
my $b = ObjB::new(split("\t",$line));
push #x,$b->form;
extract_sentence($stream,#x);
}
# OBJECT ACCESSOR METHODS
sub language {$_[0]->{language}=$_[1] if defined $_[1] ; $_[0]->{language}}
1;
And here is the one that produces the error :
package ObjB;
use warnings;
use strict;
use Data::Dumper;
use Carp;
# CONSTRUCTOR AND INITIALISATION
sub new {
my $class = shift;
my $self = {#_};
bless($self,$class);
$self->idx(); # Dies here.
return $self;
}
# OBJECT ACCESSOR METHODS
sub idx {$_[0]->{idx}=$_[1] if defined $_[1] ; $_[0]->{idx}}
1;
Would it be because ObjB is called inside ObjA ? Or because they are declared in two different files ?
I truly hope someone will have an answer because I have been running in circles ...
Thank you !!
Obj::new is a method, but you call it as a subroutine.
ObjB::new(split("\t",$line));
This results in the value of first field of the line being used as the class, and that value is probably 1. You probably meant to use
ObjB->new(split("\t",$line));

Is a cyclic dependency solved with require?

I noticed that I had a cyclic dependency in 2 of my modules. So I did the following:
package A::B::ModuleA;
sub foo {
my ($class, $params) = #_;
# some processing
require A::C::ModuleB;
my $mb = A::C::ModuleB->new();
$mb->bar($params);
# some other processing
}
1;
package A::C::ModuleB;
sub process {
my ($class, $input) = #_;
# Some processing
require A::B::ModuleA;
my $ma = A::B::ModuleA;
$ma->submit($input);
# some other processing
}
1;
So my question is, if the way that I have addressed the cyclic dependency problem via the require inside the function solves any kind of issue that could be a result of such a dependency.
For purely object-oriented code, there is no circular dependency problem. You can quite happily have something like:
# AAAA.pm
package AAAA;
use strict;
use warnings;
use BBBB;
sub new {
my $class = shift;
my ($i) = #_;
bless {
b => $i > 0 ? BBBB->new($i-1) : $i
}, $class;
}
1;
# BBBB.pm
package BBBB;
use strict;
use warnings;
use AAAA;
sub new {
my $class = shift;
my ($i) = #_;
bless {
a => $i > 0 ? AAAA->new($i-1) : $i
}, $class;
}
1;
# script.pl
use strict;
use warnings;
use AAAA;
use Data::Dumper;
print Dumper( AAAA->new(4) );
Circular dependencies only become an issue if you need do something with a module at compile-time. Exporters are the most common example of this.

Perl OO Question, Inheritance - calling parent method

Why am I not able to call testmethod of parent using child object in the following code?
use strict;
use Data::Dumper;
my $a = C::Main->new('Email');
$a->testmethod();
package C::Main;
sub new {
my $class = shift;
my $type = shift;
$class .= "::" . $type;
my $fmgr = bless {}, $class;
$fmgr->init(#_);
return $fmgr;
}
sub init {
my $fmgr = shift;
$fmgr;
}
sub testmethod {
print "SSS";
}
package C::Main::Email;
use Net::FTP;
#C::Main::Email::ISA = qw( C::Main );
sub init {
my $fmgr = shift;
my $ftp = $fmgr->{ftp} = Net::FTP->new( $_[0] );
$fmgr;
}
package C::Main::FTP;
use strict;
use Net::FTP;
#C::Main::Email::FTP = qw( C::Main );
sub init {
my $fmgr = shift;
$fmgr;
}
It is because assignment into #ISA is done at runtime, thus after you try to call the method.
You can make it work by surrounding by BEGIN, moving it to compile time:
BEGIN { our #ISA = qw( C::Main ) }
or you can do
use base qw( C::Main );
which is also done in compile time. Both variants do fix your problem.
If you're writing new OO code in Perl, use Moose!
Returning to 'use base' after having used Moose is like going back to the 1950s.

How do I read args passed to the constructor and args passed by `use Module` in Perl?

Currently I am making a new module and I was wondering how could I implement in my module 2 things.
We often see the use like:
use My::Module qw(something);
for example:
use CGI::Carp qw(fatalsToBrowser);
So the first question is, how do i
retrieve this, i mean wether the
user has specified anything and what
he specified ?
Second question is, How do i pass and read the args
directly on the constructor like
this:
my $my_module = My::Module->new(arg1,arg2,arg3);
AS requested on the comment the simple module test code:
package My::Module;
# $Id$
use strict;
use Carp;
sub new {
my $class = shift;
my $self = {};
$self->{ARG1} = undef;
$self->{ARG2} = undef;
$self->{ARG3} = undef;
$self->{ARG4} = undef;
bless($self,$class);
return $self;
}
sub arg1 {
my $self = shift;
if (#_) { $self->{ARG1} = shift }
return $self->{ARG1};
}
sub arg2 {
my $self = shift;
if (#_) { $self->{ARG2} = shift }
return $self->{ARG2};
}
sub arg3 {
my $self = shift;
if (#_) { $self->{ARG3} = shift }
return $self->{ARG3};
}
sub arg4 {
my $self = shift;
if (#_) { $self->{ARG4} = shift }
return $self->{ARG4};
}
sub dump {
my $self = shift;
require Data::Dumper;
my $d = Data::Dumper->new([$self], [ref $self]);
$d->Deepcopy(1);
return $d->Dump();
}
1; # so the require or use succeeds
perldoc -f use explains that the use keyword is simply loading a module during compile-time, and then calling ->import on it. The arguments a caller gave to the use statement will be passed to the import method call.
As for your second question: constructors are just methods. Getting their arguments works like it does for any other method or function, using the #_ variable.
import subroutine gets the arguments passed in a use. The following code samples should help you.
File: My/Module.pm
package My::Module;
use warnings;
use strict;
use Data::Dumper;
sub import {
my ( $package, #args ) = #_;
print Dumper \#args;
}
1;
File: module.pl
#!/usr/bin/env perl
use warnings;
use strict;
use My::Module qw(something);
If you are programming an object oriented module, you may try Moose which will save you lots of time.