Home > Archive > PERL Beginners > June 2007 > Proper class setup?
You are viewing an archived Text-only version of the thread.
To view this thread in it's original format and/or if you want to reply to
this thread please [click here]
| Author |
Proper class setup?
|
|
| Mathew Snyder 2007-06-22, 7:59 am |
| I'm presently learning OOP as Perl does it using online resources and and
Programming Perl as my tutors. I'm not certain I have it right though. Is this
correct for the package:
package Report;
require Exporter;
use strict;
our @ISA = qw(Exporter);
our @EXPORT = qw(new);
sub new {
my ($class) = @_;
my $self = {
_id => undef,
_queue => undef,
_owner => undef,
_priority => undef,
_worked => undef,
_timeLeft => undef,
_due => undef,
_created => undef,
_updated => undef,
_severity => undef,
_ccl => undef
};
bless $self, $class;
return $self;
}
# Accessor method for Reports _id
sub id {
my ($self, $id) = @_;
$self->{_id} = $id if defined($id);
return $self->{_id};
}
# Accessor method for Reports _queue
sub queue {
my ($self, $queue) = @_;
$self->{_queue} = $queue if defined($queue);
return $self->{_queue};
}
# Accessor method for Reports _owner
sub owner {
my ($self, $owner) = @_;
$self->{_owner} = $owner if defined($owner);
return $self->{_owner};
}
# Accessor method for Reports _owner
sub priority {
my ($self, $priority) = @_;
$self->{_priority} = $priority if defined($priority);
return $self->{_priority};
}
# Accessor method for Reports _owner
sub worked {
my ($self, $worked) = @_;
$self->{_worked} = $worked if defined($worked);
return $self->{_worked};
}
# Accessor method for Reports _owner
sub timeLeft {
my ($self, $timeLeft) = @_;
$self->{_timeLeft} = $timeLeft if defined($timeLeft);
return $self->{_timeLeft};
}
# Accessor method for Reports _owner
sub due {
my ($self, $due) = @_;
$self->{_due} = $due if defined($due);
return $self->{_due};
}
# Accessor method for Reports _owner
sub created {
my ($self, $created) = @_;
$self->{_created} = $created if defined($created);
return $self->{_created};
}
# Accessor method for Reports _owner
sub updated {
my ($self, $updated) = @_;
$self->{_updated} = $updated if defined($updated);
return $self->{_updated};
}
# Accessor method for Reports _owner
sub severity {
my ($self, $severity) = @_;
$self->{_severity} = $severity if defined($severity);
return $self->{_severity};
}
# Accessor method for Reports _owner
sub ccl {
my ($self, $ccl) = @_;
$self->{_ccl} = $ccl if defined($ccl);
return $self->{_ccl};
}
sub print {
my ($self) = @_;
# Print Report info
print $self->id . " " . $self->queue . "\n";
}
1;
Mathew
--
Keep up with me and what I'm up to: http://theillien.blogspot.com
| |
| Dr.Ruud 2007-06-22, 7:59 am |
| Mathew Snyder schreef:
> I'm presently learning OOP as Perl does it using online resources and
> and Programming Perl as my tutors. I'm not certain I have it right
> though. Is this correct for the package:
[whitespace is cheap]
> package Report;
>
> require Exporter;
> use strict;
>
> our @ISA = qw(Exporter);
> our @EXPORT = qw(new);
Exporting is not necessary, you can leave these two lines out.
> sub new {
> my ($class) = @_;
> my $self = {
> _id => undef,
> _queue => undef,
> _owner => undef,
> _priority => undef,
> _worked => undef,
> _timeLeft => undef,
> _due => undef,
> _created => undef,
> _updated => undef,
> _severity => undef,
> _ccl => undef
> };
> bless $self, $class;
> return $self;
> }
Change your new() in a new() and an init(). Do as little as possible in
your new(), for the sake of inheritance etc.
Although there is no real need to prepare the hash with undef values. So
"my $self = {};" suffices, and keeps your objects lean.
> # Accessor method for Reports _id
> sub id {
> my ($self, $id) = @_;
> $self->{_id} = $id if defined($id);
> return $self->{_id};
> }
>
> # Accessor method for Reports _queue
> sub queue {
> my ($self, $queue) = @_;
> $self->{_queue} = $queue if defined($queue);
> return $self->{_queue};
> }
>
> # Accessor method for Reports _owner
> sub owner {
> my ($self, $owner) = @_;
> $self->{_owner} = $owner if defined($owner);
> return $self->{_owner};
> }
>
> # Accessor method for Reports _owner
> sub priority {
> my ($self, $priority) = @_;
> $self->{_priority} = $priority if defined($priority);
> return $self->{_priority};
> }
>
> # Accessor method for Reports _owner
> sub worked {
> my ($self, $worked) = @_;
> $self->{_worked} = $worked if defined($worked);
> return $self->{_worked};
> }
>
> # Accessor method for Reports _owner
> sub timeLeft {
> my ($self, $timeLeft) = @_;
> $self->{_timeLeft} = $timeLeft if defined($timeLeft);
> return $self->{_timeLeft};
> }
>
> # Accessor method for Reports _owner
> sub due {
> my ($self, $due) = @_;
> $self->{_due} = $due if defined($due);
> return $self->{_due};
> }
>
> # Accessor method for Reports _owner
> sub created {
> my ($self, $created) = @_;
> $self->{_created} = $created if defined($created);
> return $self->{_created};
> }
>
> # Accessor method for Reports _owner
> sub updated {
> my ($self, $updated) = @_;
> $self->{_updated} = $updated if defined($updated);
> return $self->{_updated};
> }
>
> # Accessor method for Reports _owner
> sub severity {
> my ($self, $severity) = @_;
> $self->{_severity} = $severity if defined($severity);
> return $self->{_severity};
> }
>
> # Accessor method for Reports _owner
> sub ccl {
> my ($self, $ccl) = @_;
> $self->{_ccl} = $ccl if defined($ccl);
> return $self->{_ccl};
> }
I see a pattern there. :)
These 11 methods are all doing basically the same, right?
So consider a set/get approach.
The "if defined($value)" parts are not really necessary, but it's good
that they are there: your class could, for example, be used in an
environment where dirtying the cache can hurt performance.
There can be one problem though: what if you want to change a property's
value to undef? :)
--
Affijn, Ruud
"Gewoon is een tijger."
| |
| Mathew Snyder 2007-06-22, 7:59 am |
| Dr.Ruud wrote:
> Mathew Snyder schreef:
>
>
> [whitespace is cheap]
Duly noted :D
>
>
> Exporting is not necessary, you can leave these two lines out.
>
>
>
> Change your new() in a new() and an init(). Do as little as possible in
> your new(), for the sake of inheritance etc.
>
I'm not sure what you mean by "Change your new() in a new() and init()".
> Although there is no real need to prepare the hash with undef values. So
> "my $self = {};" suffices, and keeps your objects lean.
>
>
>
>
> I see a pattern there. :)
> These 11 methods are all doing basically the same, right?
> So consider a set/get approach.
>
What do you mean by "set/get approach"?
> The "if defined($value)" parts are not really necessary, but it's good
> that they are there: your class could, for example, be used in an
> environment where dirtying the cache can hurt performance.
> There can be one problem though: what if you want to change a property's
> value to undef? :)
>
| |
| Chas Owens 2007-06-22, 9:59 pm |
| On 6/22/07, Mathew Snyder <theillien@yahoo.com> wrote:
snip
> I'm not sure what you mean by "Change your new() in a new() and init()".
snip
> What do you mean by "set/get approach"?
snip
Some people believe that new should just create a new blank object and
call an init method to do setup. There are good arguments both ways.
Manually writing Accessor, Mutator, Getter, or Setter methods sucks
and is error prone. It is often better to write one getter and one
setter that gets or sets the field(s) passed to it. Another method is
to use Perl's autoload capability to magically create subroutines for
you. Starting with some Perl in the 5.8 line the autoload function
gained the ability to be an lvalue, so I have written it that way.
Perl script:
#!/usr/bin/perl
use strict;
use warnings;
use Report;
my $rpt = Report->new;
$rpt->set({id => 5, queue => 10});
print $rpt->printable;
my ($id, $queue) = $rpt->get(qw(id queue));
print "id is $id and queue is $queue\n";
#same thing, but I like the => better
$rpt->set("id", 6);
$rpt->set(id => 6);
print "id is now ", $rpt->get("id"), "\n";
$rpt->id = 7;
print "id is now ", $rpt->id, "\n";
Module:
package Report;
use strict;
use warnings;
use Carp;
our $AUTOLOAD;
our %fields = (
_id => 1,
_queue => 1,
_owner => 1,
_priority => 1,
_worked => 1,
_timeLeft => 1,
_due => 1,
_created => 1,
_updated => 1,
_severity => 1,
_ccl => 1,
);
#minimal new
sub new {
my $class = shift;
my $self = bless {}, $class;
$self->init(@_);
return $self;
}
#real object creation happens here
sub init {
my $self = shift;
my @fields = keys %fields;
@{$self}{@fields} = (undef) x @fields;
}
sub _validate_field {
my ($self, $k) = @_;
croak "$k is not a valid field for " . ref $self
unless $fields{"_$k"};
}
#Getter/setter method 1
sub get {
my ($self, @k) = @_;
my @ret;
for my $k (@k) {
$self->_validate_field($k);
push @ret, $self->{"_$k"};
}
local $" = ' ::: ';
return @ret
}
sub set {
my $self = shift;
croak "bad number of arguments" unless @_ == 2 or @_ == 1;
if (@_ == 2) {
$self->_validate_field($_[0]);
return $self->{"_$_[0]"} = $_[1];
}
croak "not a hash reference" unless ref $_[0] eq 'HASH';
my $h = $_[0];
my @ret;
for my $k (keys %$h) {
$self->_validate_field($k);
push @ret, $self->{"_$k"} = $h->{$k};
}
return @ret;
}
#another form of setter/getter
sub AUTOLOAD : lvalue {
my ($k) = $AUTOLOAD =~ /::(.*?)$/;
return if $k eq 'DESTROY';
my $self = shift;
$self->_validate_field($k);
$self->{"_$k"};
}
sub printable {
my ($self) = @_;
# return Printable Report info
return $self->id . " " . $self->queue . "\n";
}
1;
| |
| Mathew Snyder 2007-06-24, 7:59 am |
| Chas Owens wrote:
> On 6/22/07, Mathew Snyder <theillien@yahoo.com> wrote:
> snip
> snip
> snip
>
> Some people believe that new should just create a new blank object and
> call an init method to do setup. There are good arguments both ways.
>
> Manually writing Accessor, Mutator, Getter, or Setter methods sucks
> and is error prone. It is often better to write one getter and one
> setter that gets or sets the field(s) passed to it. Another method is
> to use Perl's autoload capability to magically create subroutines for
> you. Starting with some Perl in the 5.8 line the autoload function
> gained the ability to be an lvalue, so I have written it that way.
>
> Perl script:
> #!/usr/bin/perl
>
> use strict;
> use warnings;
> use Report;
>
> my $rpt = Report->new;
>
> $rpt->set({id => 5, queue => 10});
> print $rpt->printable;
>
> my ($id, $queue) = $rpt->get(qw(id queue));
>
> print "id is $id and queue is $queue\n";
>
> #same thing, but I like the => better
> $rpt->set("id", 6);
> $rpt->set(id => 6);
>
> print "id is now ", $rpt->get("id"), "\n";
>
> $rpt->id = 7;
> print "id is now ", $rpt->id, "\n";
>
> Module:
> package Report;
>
> use strict;
> use warnings;
> use Carp;
>
> our $AUTOLOAD;
> our %fields = (
> _id => 1,
> _queue => 1,
> _owner => 1,
> _priority => 1,
> _worked => 1,
> _timeLeft => 1,
> _due => 1,
> _created => 1,
> _updated => 1,
> _severity => 1,
> _ccl => 1,
> );
>
> #minimal new
> sub new {
> my $class = shift;
> my $self = bless {}, $class;
> $self->init(@_);
> return $self;
> }
>
> #real object creation happens here
> sub init {
> my $self = shift;
> my @fields = keys %fields;
> @{$self}{@fields} = (undef) x @fields;
> }
>
> sub _validate_field {
> my ($self, $k) = @_;
> croak "$k is not a valid field for " . ref $self
> unless $fields{"_$k"};
> }
>
> #Getter/setter method 1
> sub get {
> my ($self, @k) = @_;
> my @ret;
> for my $k (@k) {
> $self->_validate_field($k);
> push @ret, $self->{"_$k"};
> }
> local $" = ' ::: ';
> return @ret
> }
>
> sub set {
> my $self = shift;
> croak "bad number of arguments" unless @_ == 2 or @_ == 1;
> if (@_ == 2) {
> $self->_validate_field($_[0]);
> return $self->{"_$_[0]"} = $_[1];
> }
> croak "not a hash reference" unless ref $_[0] eq 'HASH';
> my $h = $_[0];
> my @ret;
> for my $k (keys %$h) {
> $self->_validate_field($k);
> push @ret, $self->{"_$k"} = $h->{$k};
> }
> return @ret;
> }
>
> #another form of setter/getter
>
> sub AUTOLOAD : lvalue {
> my ($k) = $AUTOLOAD =~ /::(.*?)$/;
> return if $k eq 'DESTROY';
> my $self = shift;
> $self->_validate_field($k);
> $self->{"_$k"};
> }
>
> sub printable {
> my ($self) = @_;
>
> # return Printable Report info
> return $self->id . " " . $self->queue . "\n";
> }
>
> 1;
>
I pretty much have a very small idea of what is going on up there.
Mathew
Keep up with me and what I'm up to: http://theillien.blogspot.com
| |
| Chas Owens 2007-06-24, 6:59 pm |
| On 6/24/07, Mathew Snyder <theillien@yahoo.com> wrote:
snip
>
> I pretty much have a very small idea of what is going on up there.
>
snip
Alright, lets walk through it piece by piece then.
[color=darkred]
We need to know which fields are valid a couple of times, so it is
helpful to move this information out into a class variable.
[color=darkred]
This is an implementation of what Dr. Ruud was talking about. The new
method creates a new, blank object of the calling class and call's
that classes init to build it out. The only tricky thing here is a
use of a hash slice and the list repetition operator to save some
typing. The line
@{$self}{@fields} = (undef) x @fields;
could also be written
for my $field (@fields) {
$self->{$field} = undef;
}
[color=darkred]
This is a helper method (which is why it has an _ at the start of its
name). If the field name passed to it is not in %fields then it will
croak telling the user that he/she used an invalid field name.
[color=darkred]
This is an implementation of what I think Dr. Ruud was talking about
(a getter method). You can pass in one or more field names and the
method will return the values associated with them in the calling
object. I don't think there is anything tricky going on here, but
there is a piece of debug code that was accidentally left in that may
cause confusion. The line
local $" = ' ::: ';
can and should be removed. It was just for testing purposes.
[color=darkred]
Again, this is an implementation of the setter that I think Dr. Ruud
was talking about. It has two different forms. The first takes two
arguments and the second takes only one. In the first version (@_ ==
2), the first argument is the field name to set and the second
argument is the value to set. The second version (@_ == 1) expects a
hashref whose keys are the field names to set and whose values are the
values to set. I don't see anything tricky going on here, but please
ask about anything you don't understand
[color=darkred]
Remember how I said I didn't see anything tricky going on before?
Well, this is tricky. There are two separate tricky things going on
here: AUTOLOAD and lvalue subroutines. In perldoc perlsub you will
find lots of information about AUTOLOAD, but here is the basic idea:
if a subroutine named AUTOLOAD exists then it will be called whenever
someone tries to call a subroutine in that package that does not
exist. So, if I say
$obj->foo;
and there is not a corresponding subroutine named foo in $obj's
package (or any of the packages in @ISA) then the subroutine AUTOLOAD
will be called. The arguments to foo will be passed to AUTOLOAD and
the fully qualified name of the function/method that was called will
be placed in $AUTOLOAD (in this case OBJCLASS::foo).
So, the upshot of all of this is that we are catching the subroutine
names that match fields in our object and returning the value of that
field.
That takes care of the first part of the magic. The second part is
lvalue subroutines. An lvalue is anything that is valid on the left
side of an assignment (hence lvalue). So, some common lvalues are
scalars, arrays, hashes, array elements, and hash elements.
$lvalue = 0;
@lvalue = (1, 2, 3);
%lvalue = (a => 1, b => 2, c => 3);
$lvalue[3] = 4;
$lvalue{d} = 4;
An odd case of something that is an lvalue is the trinary operator ?:
($lvalue == 0 ? $lvalue[0] : $lvalue{a}) = 0;
This will set either $lvalue[0] or $lvalue{a} depending on what is in $lvalue.
Subroutines are not lvalues by default, but there is experimental
support for making them so. If you set the attribute "lvalue" when
you create the subroutine and end the subroutine with a scalar value
($self->{"_$k"}; in the code above) then that value is the target for
the assignment operator. You can learn more in perldoc perlsub.
[color=darkred]
This function is too mundane to explain.
|
|
|
|
|