Perl OOP attribute manipulation best practice
Assume the following code:
package Thing;
sub new {
my $this=shift;
bless {@_},$this;
}
sub name {
my $this=shift;
if (@_) {
$this->{_name}=shift;
}
return $this->{_name};
}
Now assume we've instantiated an object thusly:
my $o=Thing->new();
$o->name('Harold');
Good enough. We could also instantiate the same thing more quickly with
either of the following:
my $o=Thing->new(_name=>'Harold'); # poor form
my $o=Thing->new()->name('Harold');
To be sure, I allowed attributes to be passed in the constructor to allow
"friendly" classes to create objects more completely. It could also allow
for a clone-type operator with the following code:
my $o=Thing->new(%$otherthing); # will clone attrs if not deeper than 1
level
This is all well and good. I understand the need for hiding attributes
behind methods to allow for validation, etc.
$o->name; # returns 'Harold'
$o->name('Fred'); # sets name to 'Fred' and returns 'Fred'
But what this doesn't allow is easy manipulation of the attribute based on
itself, such as:
$o->{_name}=~s/old/ry/; # name is now 'Harry', but this "exposes" the
attribute
One alternative is to do the following:
# Cumbersome, not syntactically sweet
my $n=$o->name;
$n=~s/old/ry/;
$o->name($n);
Another potential is the following method:
sub Name :lvalue { # note the capital 'N', not the same as name
my $this=shift;
return $this->{_name};
}
Now I can do the following:
$o->Name=~s/old/ry/;
So my question is this... is the above "kosher"? Or is it bad form to
expose the attribute that way? I mean, doing that takes away any
validation that might be found in the 'name' method. For example, if the
'name' method enforced a capital first letter and lowercase letters
thereafter, the 'Name' (capital 'N') bypasses that and forces the user of
the class to police herself in the use of it.
So, if the 'Name' lvalue method isn't exactly "kosher" are there any
established ways to do such things?
I have considered (but get dizzy considering) things like tied scalars as
attributes. To be sure, it may be the way to go.
Also, are there perhaps overloads that may help?
Or should I create replacement methods in the vein of (if it would even
work):
sub replace_name {
my $this=shift;
my $repl=shift;
my $new=shift;
$this->{_name}=~s/$repl/$new/;
}
...
$o->replace_name(qr/old/,'ry');
Thanks in advance... and note, I am not very experienced in Perl's brand
of OOP, even though I am fairly well-versed in OOP itself.
Additional info: I guess I could get really creative with my interface...
here's an idea I tinkered with, but I guess it shows that there really are
no bounds:
sub name {
my $this=shift;
if (@_) {
my $first=shift;
if (ref($first) eq 'Regexp') {
my $second=shift;
$this->{_name}=~s/$first/$second/;
}
else {
$this->{_name}=$first;
}
}
return $this->{_name};
}
Now, I can either set the name attribute with
$o->name('Fred');
or I can manipulate it with
$o->name(qr/old/,'ry'); # name is now Harry
This still doesn't allow stuff like $o->name.=' Jr.'; but that's not too
tough to add. Heck, I could allow calllback functions to be passed in,
couldn't I?
No comments:
Post a Comment