In the thread 'Set' Method Return Value, I recommended that members that can be set and got should be accessed directly (or tied to hide the accessors), rather than having a method-based API to set and get them. This idea was downvoted (from what I surmise) because it exposes the implementation.
But it doesn't.
With tie, a hash is just an API. And it's a good API for any object that has settable-gettables, because it's familiar and can be used in all the ways that Perl users know and love (subject to any restrictions you program in, of course). It also encapsulates your object completely: all access is necessarily through the API.
That's it. What follows is some example code of minimal objects implemented in the getter-setter way, and then using a hash API. You can see that the code is very similar. Both implementations use an array as the actual base type.
Update: I should add that the code below is not intended to be a blueprint for writing classes. It is only to illustrate the notion that a hash can be an API rather than an implementation. There are a number of issues to address (pointed out by respondants already) to make a workable, general-purpose foundation.
use warnings;
use strict;
package MyThing;
sub new {
my $class = shift;
bless [], $class;
}
sub set_foo {
my ($self, $newfoo) = @_;
$self->[0] = $newfoo;
}
sub get_foo {
$_[0]->[0];
}
package HashAPI;
sub TIEHASH {
my $class = shift;
bless [], $class;
}
sub STORE {
my ($self, $key, $val) = @_;
unless ($key eq 'foo') {
die "No $key member\n";
}
$self->[0] = $val;
}
sub FETCH {
my ($self, $key, $val) = @_;
unless ($key eq 'foo') {
die "No $key member\n";
}
$self->[0];
}
sub new {
my $class = shift;
tie my(%obj), $class;
bless \%obj, $class;
}
package main;
my $clunker = MyThing->new();
my $slick = HashAPI->new();
$clunker->set_foo('Some value');
print $clunker->get_foo, "\n";
$slick->{foo} = 'Some value';
print $slick->{foo}, "\n";
Caution: Contents may have been coded under pressure.
Re: tie for Perlish, encapsulated objects
by perrin (Chancellor) on Nov 24, 2005 at 02:04 UTC
|
Sorry, but this is a bad idea. SPOPS does it this way, and it leads to a lot of confusion. People look at the code and say "Why are you accessing this object as a hash?" It's just a lot of needless indirection for people trying to maintain your code. It's also measurably slower than just calling accessors. | [reply] |
Re: tie for Perlish, encapsulated objects
by Zaxo (Archbishop) on Nov 23, 2005 at 20:48 UTC
|
I mainly agree, tie is an excellent way to encapsulate data and complicated behaviors. The encapsulation is not as tight as you suggest, though. The tied call returns the underlying blessed object. No B&D OO in Perl!
Your example is perhaps a little too simple. The hardwired key in your setter and getter has to be rewritten for each class you want. That could be generalized by allowing the constructor to accept a list of keys and then generate the FETCH and STORE methods as per-instance CODE refs. The class FETCH() and STORE() would then just call the instance's stored method.
That begins to sound a lot like a pseudohash, but with a completely different implementation.
The alternative is to make FETCH() virtual by declaring but not defining it. The user could then subclass your Tie:: class and define it there, or else define it externally.
| [reply] |
Re: tie for Perlish, encapsulated objects
by jdhedden (Deacon) on Nov 23, 2005 at 21:36 UTC
|
This results in an inconsistent interface combining hash access:
$obj->{field}
with object methods:
$obj->action()
Remember: There's always one more bug.
| [reply] [d/l] [select] |
|
| [reply] |
|
| [reply] |
|
|
|
Most OO design writing I've seen says that accessor methods are supposed to hide the existence of member variables, in case the implementation of the class changes.
| [reply] |
|
|
|
Re: tie for Perlish, encapsulated objects
by radiantmatrix (Parson) on Nov 25, 2005 at 20:44 UTC
|
This just seems like a fancy wrapper to $instance->{attribute} notation. Yeah, you can make assignments to more complicated things, but you can do that with overload as well. I guess I just don't get it.
I've fallen in the habit of implementing this type of thing for set and get operations:
package SomePackage;
use base 'Class::Base';
## super-simple 'new'
sub new {
my $self = shift;
bless { one => 1, two => 2 }, $self;
}
sub set {
my $self = shift;
my $attrib = shift;
return $self->error("No such attribute '$attrib'")
unless exists $self->{$attrib}
my $value = shift;
# check to make sure we're setting the same kind of reference, if i
+t matters
if (ref $self->{$attrib} && ref $value ne ref $self->{$attrib}) {
return $self->error("Reference types do not match for '$attrib'
+set.")
}
$self->{$attrib} = $value;
return 1; # true on success
}
sub get {
my ($self, $attrib) = @_;
return $self->error("Attribute '$attrib' doesn't exist")
unless exists $self->{$attrib};
my $value = $self->{$attrib};
return $value;
}
1;
This means I can do $instance->set('one', 'Hey there!'); and $instance->get('two');. If certain attributes have special rules to validate data, I can deal with those by altering 'set' slightly. For example:
# set from above gets renamed _set_DEFAULT
sub set {
my ($self, $attrib, $value) = @_;
if ( $self->can('_set_'.$attrib) ) {
my $result;
eval "\$result = \$self->_set_$attrib($value)";
return $result;
}
else {
return $self->_set_DEFAULT($attrib, $value);
}
}
Something like that is much cleaner and clearer in the implmentation. I like the idea that when I see $instance->{attrib} = 2, I know the code is messing about with something advanced, while $instance->set('attrib', 2) is playing by the rules.
Besides, with Perl6's advancements in lvalue subs, it will be possible to do something like $instance->accessor('attrib') = 2, and we both win, so I don't see much point in debating the tie methodology.
| [reply] [d/l] [select] |
Re: tie for Perlish, encapsulated objects
by Perl Mouse (Chaplain) on Nov 23, 2005 at 21:02 UTC
|
How would you do inheritance using the tied hashes?
| [reply] |
Ties are can be very, very confusing...
by Anonymous Monk on Nov 25, 2005 at 20:42 UTC
|
Suppose you have this code fragment:
$x=$x+1;
and you know that $x is a tied object. Quick, what does the code do?
Who knows?
All I know is, it will call the appropriate fetch and store functions in the tied package, and something will happen. Without reading backwards to find the tie command, it's not obvious which functions in which package are being called.
When I find out code has tied objects, I always cringe inside, because I know I'll have to audit every single line for potential tied side effects to be assured that nothing stupid is happening. I've seen some badly written code where the tied variables are tied in completely different modules from the modules where they are used, so it's a nightmare trying to figure out exactly what is going on.
I'd rather just see a normal function call interface; just explictly call the equivalents of $class->TIESCALAR(), $object->FETCH(). | [reply] [d/l] |
|
|