Data Encapsulation in Perl?
Hello Perl community on SO. I am using Perl since a few years, but since I am following SO, I recognized that I know Perl not enough.
I wrote I quite big script over the past 4 years and tried to do this in OO style. I know that Perl<6 is not really OO.
So one point I don't like is that I have no data encapsulation, that means no Variables that are really private to a package ("class") (or maybe I don't know how to do it).
I have something like this (only a small part of my script)
package TAG;
sub new () {
my $classname = shift;
my $self = {};
bless( $self, $classname );
$self->initialize();
return $self;
}
sub initialize() {开发者_如何学编程
my $self = shift;
# Only an example, I have a long list of items in this "class"
$self->{ID} = "NA";
}
sub setID() {
...
}
sub getID() {
...
}
In my main script I am using it then this way:
my $CurrentItem;
$CurrentItem = new TAG();
$CurrentItem->getID()
but
$CurrentItem->{ID} = "Something";
is also working, but I would prefer, that this is not possible.
Is there a way to get a better encapsulation of the data I am using in the "class", so that I am (or other users are) forced to use the get and set methods?
This is an issue that's been discussed in several places, and there are several possible work-arounds, but none of them are necessarily ideal.
This paper discusses options such as closures, scalars, and a limited access hash using Tie::SecureHash, preferring the last approach.
This blog argues that in perl there are times when encapsulation should be violated, although the comments bring up some negatives for doing so.
You can also look into moose for your Perl 5 objects. It's written to encourage the use of encapsulated objects.
Perl supports part of encapsulation: delegation and information hiding. Also another part of encapsulation is simply what I call "behavior-bounded data", associating data with behavior. In the Wikipedia article on "Encapsulation (object-oriented programming)" it suggests "encapsulation is used to refer to one of two related but distinct notions" (italics mine). The second one listed is
- A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.
A good part of the article is "Information Hiding". Perl allows the type of OO that hides complexity as long as you don't look too hard for it. I use encapsulation all the time in Perl. There are so many problems that I solve once and use again and again, with the idea that as long as the interfacing classes don't "reach in", the behavior should be as expected.
Most dynamic languages have accomplished much with manners that heavier languages accomplish with secure encapsulation. But nonetheless, Perl allows you to assign behavior to any type of reference you want, and Inside-Out objects are probably as secure in Perl as any other form of encapsulation--although more of a chore to write, but that just gives you a case that you chose a security-to-detail trade-off for classes that need it.
You may want to try Moose ( or Mouse or Any::Moose ).
package TAG;
use Moose;
has ID => (
reader => 'getID', # this is the only one that is needed
writer => 'setID',
predicate => 'hasID',
clearer => 'clearID',
isa => 'Str', # this makes setID smarter
required => 1, # forces it to be included in new
);
has name => (
is => 'ro', # same as reader => 'name',
required => 1, # forces it to be included in new
);
# Notice that you don't have to make your own constructor.
# In fact, if you did, you would effectively break Moose
# so don't do that.
Although this doesn't actually prevent TAG->{ID}
access it does several things for you under-the-hood.
Which is roughly equivalent to:
package TAG;
use strict;
use warnings;
sub getID{
my($self) = @_;
return $self->{ID};
}
sub setID{
my($self,$new_id) = @_;
# die if $new_id is anything other than a string
# ( the one produced by Moose is smarter )
die if ref $new_id;
die unless defined $new_id;
$self->{ID} = $new_id
}
sub hasID{
my($self) = @_;
return exists $self->{ID}
}
sub clearID{
my($self) = @_;
delete $self->{ID}
}
sub name{
my($self) = @_;
return $self->{name}
}
# The constructor provided by Moose is vastly superior
# to the one shown here.
sub new{
my($class,%opt) = @_;
my $self = bless {}, $class;
die unless exists $opt{ID}; # this was the required => 1, from above
$self->setID($opt{ID});
die unless exists $opt{name}; # this was the required => 1, from above
$self->{name} = $opt{name};
}
There is a theory that states that the number of bugs in a given piece of code is proportional to the number of lines of code.
If thats true, then I would say that the Moose version has significantly fewer bugs.
Actually if I had implemented everything that Moose does for you, there would be more than twice as many lines-of-code, as there is now.
If you really need to prevent $TAG->{ID}
access, you could possibly use another meta-class, while still using Moose. Don't ask me how to do this, I only just started using Moose recently.
The most straightforward solution in perl is just to either prefix that variable with something (i.e. "_ID", or "private_ID", or anything you want) and then not document that because its not part of the interface.
There are other ways, and it can be done, but you have to ask who is going to try and break it like this and if they want to misuse your code then you are going to have trouble no matter what.
If you want encapsulation, why do you break it in your constructor? Instead for munging $self->{ID}
directly, you should be setting it with your setID
method.
You can do all sorts of funky stuff to prevent access and munging of your object internals.
One of the simplest is use a lock your object using lock_hash. This way, any attempts to modify its innards will throw a fatal error.
You can also use inside out objects.
You could overload hash access to prevent access outside child classes:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my $f = Foo->new();
$f->bar(15);
print Dumper $f;
eval {
$f->{bar} = 23;
1;
} or do {
print "Direct access error: $@";
};
print Dumper $f;
BEGIN {
package Foo;
use strict;
use warnings;
use overload '%{}' => '_HASH_DEREF';
sub new {
return bless {};
}
sub bar {
my $self = shift;
$self->{bar} = shift if @_;
return $self->{bar};
}
sub _HASH_DEREF {
my $caller = caller;
die "Illegal access to object internals"
unless $caller eq __PACKAGE__;
return shift;
}
}
You could bless subroutines that you call with the method name to generate a magic secret value that is used as an S3 bucket.
You could any bizarre thing that you can think of to hide the data.
Perl objects let you do whatever crazy thing you want on the back end to handle storage and or encapsulation.
If you have a team that cheats and directly accesses the object innards, then you have a social problem to resolve. A technical fix MAY help. The real fix is in changing your dev team's behaviors.
精彩评论