The thing is, if you have advertized that you can put a scalar in and retrieve the same scalar, you've just set in stone that, fundamentally, you've got a scalar variable. Go ahead and make it one, and let the user treat it like one. Same for an array or even a hash. If it's a settable-gettable, it's a variable and should be advertized as one.
Here's the magic in the meditation: if it's not an ordinary version of whatever data type, you can tie — and thus encapsulate — it. (Update:) This is not to say that it's always the Right Thing To Do. If you need validation performed for each store, you would have to have a separate flag to indicate error, and it would be cumbersome for the programmer. But for values that the user is free to manipulate (and especially for those that he will often update), it can be a significant improvement in convenience.
Here's some silly code to illustrate. The "password" is stored in an encrypted form in memory (presumably as a cartoonishly misguided attempt to be more secure).
package Password;
use Carp;
use strict;
my $security = 'reallysecure';
sub TIESCALAR {
my $class = shift;
my $arg;
return bless \$arg, $class;
}
sub FETCH {
my $self = shift;
confess "wrong type" unless ref $self;
croak "usage error" if @_;
return substr($$self ^ $security, 0, length($$self));
}
sub STORE {
my $self = shift;
confess "wrong type" unless ref $self;
my $newval = shift;
croak "usage error" if @_;
if (length($newval) < 5) { carp "Not long enough" }
elsif (length($newval) > 12) { carp "Too long" }
else {
$$self = substr($newval ^ $security, 0, length($newval));
# print "Stored ", join('.', unpack('H2'x length($$self), $$self)), "\n";
# print "Plain is ", join('.', unpack('H2'x length($$self), $newval)), "\n";
}
}
sub encoded_form {
my $self = shift;
$$self;
}
package Main;
my $foo; # This would be my object
my $pwref = tie $foo->{'bar'}, 'Password';
$foo->{'bar'} = 'squamous';
print $foo->{'bar'}, "\n";
$foo->{'bar'} =~ s/am/a1m/;
print $foo->{'bar'}, "\n";
print "Encoded form is ", $pwref->encoded_form, "\n";
------
We are the carpenters and bricklayers of the Information Age.
Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose
I shouldn't have to say this, but any code, unless otherwise stated, is untested
You might want to take a look at lvalued subroutines. Not only do you keep the encapsulation of using methods, but you can treat them like scalars as well.
package Test;
sub new {
my ($class, $foo, $bar) = @_;
return bless {
foo => $foo
} => ref($class) || $class;
}
sub foo : lvalue { $_[0]->{foo} }
package main;
my $test = Test->new("foo");
print $test->foo, "\n";
$test->foo = "bar";
print $test->foo, "\n";
$test->foo =~ s/bar/baz/g;
print $test->foo, "\n";
They violate encapsulation. A normal mutator can check the supplied argument before setting the attribute it is protecting, an lvalue subroutine never gets that chance.A tied scalar can check before setting. Of course, in your example, if you had tied the foo element, then you'd have encapsulation.
They violate encapsulation. A normal mutator can check the supplied argument before setting the attribute it is protecting, an lvalue subroutine never gets that chance.
Hmmm, for some reason, I thought that you could check that value. I guess that will teach me to post past my bedtime :)
I guess deep down, I want them to be like C#'s properties, but alas.....
A tied scalar can check before setting. Of course, in your example, if you had tied the foo element, then you'd have encapsulation.
While dragonchild may be mis-understanding your idea, he is correct that tied variables are a pretty sizable performance hit, and are propbably better not used in this way, where a simple getter/setter combo will do.
Of course, even better OO practice is to design classes with the minimum of getter/setters in the first place. Personally I only create them if and when I need them, so that I am not tempted to let it all hang out for no reason.
You may be interested in node 288461. There I demonstrate a way to enforce constraints on data accessed by closures on it in lvalue subs. The key was to wedge the constraint test into assignment and friends by tieing the cloistered scalar to a 'Constraint' class.
Don't worry too much about speed; if you have constraints that must be tested, you need correctness and completeness more than speed.
After Compline,
Zaxo
My intent was to address the changing-implementation issue. I am pointing out that, initially, you can use an ordinary scalar (not even tied!) if that is how your API "should" work. Later, if the implementation changes (e.g., the member needs to be stored in and retrieved from a database), you can tie it within your module, and the API doesn't change. It's a significant point that I am not advocating tie-ing every member. Only those that need to be tied.
For validation, I think that having a set method that indicates success can be preferable to relying on error generation, depending on how reasonable you think it is for the programmer to know whether he's trying to set an illegal value. It's the difference between
$foo->setval($val) or $foo->setval($alternative);
# and something like
eval { $foo->val = $val }
$@ and $foo->val = $alternative;
Something to consider.
| Plankton: 1% Evil, 99% Hot Gas. |
Rate tie mutate mutate2 getnset naked
tie 77.4/s -- -27% -36% -54% -93%
mutate 107/s 38% -- -11% -37% -91%
mutate2 120/s 55% 13% -- -29% -90%
getnset 169/s 118% 58% 40% -- -86%
naked 1181/s 1426% 1007% 882% 601% --
One note about my benchmark program: if you pass it a non-zero argument, it will pass that on to cmpthese. If you don't, it will just sample-run each method to verify that the output is sensible. Use a negative arg for seconds, as positive one for iterations.
#! perl
use strict;
package Ring;
use Carp;
sub TIESCALAR {
my $class = shift;
my $arg;
bless \$arg, $class;
}
sub FETCH {
my $self = shift;
confess "wrong type" unless ref $self;
croak "usage error" if @_;
return $$self;
}
sub STORE {
my $self = shift;
confess "wrong type" unless ref $self;
my $newval = shift;
croak "usage error" if @_;
$$self = $newval % 12;
}
package MyOb;
use Carp;
sub new {
my $class = shift;
my $struct = {
tie => 0,
getnset => 0,
mutate => 0,
naked => 0
};
tie $struct->{'tie'}, 'Ring';
bless $struct, $class;
}
sub get {
my $self = shift;
confess "wrong type" unless ref $self;
croak "usage error" if @_;
return $self->{'getnset'};
}
sub set {
my $self = shift;
confess "wrong type" unless ref $self;
my $newval = shift;
croak "usage error" if @_;
$self->{'getnset'} = $newval % 12;
}
use UNIVERSAL 'isa';
sub mutate {
my $self = shift;
confess "wrong type" unless ref $self;
if (@_) {
my $set = shift;
if (isa($set, 'CODE')) {
$set->() for $self->{'mutate'};
}
else { $self->{'mutate'} = $set }
$self->{'mutate'} %= 12;
}
$self->{'mutate'};
}
package Main;
use Benchmark ':all';
my $arg = shift;
my $ob = MyOb->new;
if ($arg) {
cmpthese($arg, {
'naked' => sub { $ob->{'naked'}++, $ob->{'naked'} %= 12 for 1..100 },
'tie' => sub { ++$ob->{'tie'} for 1..100 },
'mutate' => sub { $ob->mutate(sub{++$_}) for 1..100 },
'getnset' => sub { $ob->set($ob->get + 1) for 1..100 },
'mutate2' => sub { $ob->mutate($ob->mutate + 1) for 1..100 },
});
}
else {
print 'Naked: ';
&{sub { $ob->{'naked'}++, $ob->{'naked'} %= 12 for 1..100 }};
print $ob->{'naked'},"\n";
print 'Tie: ';
&{sub { ++$ob->{'tie'} for 1..100 }};
print $ob->{'tie'}, "\n";
print 'Mutate: ';
&{sub { $ob->mutate(sub{++$_}) for 1..100 }};
print $ob->mutate, "\n";
print 'GetNSet: ';
&{sub { $ob->set($ob->get + 1) for 1..100 }};
print $ob->get, "\n";
$ob->mutate(0);
print 'Mutate: ';
&{sub { $ob->mutate($ob->mutate + 1) for 1..100 }};
print $ob->mutate, "\n";
}
perlmonks.org content © perlmonks.org and dragonchild, Plankton, Roy Johnson, stvn, Zaxo
prlmnks.org © 2006 edmund von der burg (eccles & toad)
v 0.03