Fellow monks, I've been thinking about access to my fields and came up with the following.
I'm thinking of publishing this little work in the tutorials section if enough feedback warrants it of course :)
Many perl objects are created by using hash references, be it the normal perl OO way of doing things or by using Inside-Out classes. However, I prefer using closures because I can define private and protected fields with ease.
Objects with Private Variables provides a nice introduction to closures and talks about private fields. I suggest you read Objects with Private Variables first to get a short introduction in closures and private access.
A few definitions
The class Duck defines the basic behaviour of a Duck. A duck can fly and/or quack. See Re: A quicker way to have protected and private fields? for more information
package src::bo::Duck; use strict; use warnings; use Carp; sub new { my $class = shift; my $self = { FLYBEHAVIOUR => { 'VALUE' => undef, 'ACCESS' => 'protected' }, QUACKBEHAVIOUR => { 'VALUE' => undef, 'ACCESS' => 'protected' +}, }; my $closure = sub { my $field = shift; $self->{$field}->{'ACCESS'} eq 'private' and caller(0) eq __PACKAGE__ || confess "$field is private"; $self->{$field}->{'ACCESS'} eq 'protected' and caller(0)->isa(__PACKAGE__) || confess "$field is protected"; if ( @_ ) { $self->{$field}->{'VALUE'} = shift; } return $self->{$field}->{'VALUE'}; }; bless ($closure, $class); return $closure; } sub setFlyBehaviour { caller(0)->isa(__PACKAGE__) || confess "setFlyBehaviour is protect +ed"; my $closure = shift; my $flyBehaviour = shift; &{ $closure }("FLYBEHAVIOUR", $flyBehaviour); } sub setQuackBehaviour { caller(0)->isa(__PACKAGE__) || confess "setQuackBehaviour is prote +cted"; my $closure = shift; my $quackBehaviour = shift; &{ $closure }("QUACKBEHAVIOUR", $quackBehaviour); } sub doFly { my $closure = shift; &{ $closure }("FLYBEHAVIOUR")->fly(); } sub doQuack() { my $closure = shift; &{ $closure }("QUACKBEHAVIOUR")->quack(); } 1;
FLYBEHAVIOUR and QUACKBEHAVIOUR are protected fields. These fields are only accesible within Duck.pm are the classes that subclass Duck.pm.
FLYBEHAVIOUR and QUACKBEHAVIOUR are implemented as simple hashes. Both fields have two hash entries. The entry for 'VALUE' specifies the value of our fields while 'ACCESS' specifies private and/or protected access.
my $self = { FLYBEHAVIOUR => { 'VALUE' => undef, 'ACCESS' => 'protected' }, QUACKBEHAVIOUR => { 'VALUE' => undef, 'ACCESS' => 'protected' +}, };
Our constructor is a bit special. It contains a closure that gets blessed as a class of the type Duck. The closure does all the checking for field access.
my $closure = sub { my $field = shift; $self->{$field}->{'ACCESS'} eq 'private' and caller(0) eq __PACKAGE__ || confess "$field is private"; $self->{$field}->{'ACCESS'} eq 'protected' and caller(0)->isa(__PACKAGE__) || confess "$field is protected"; if ( @_ ) { $self->{$field}->{'VALUE'} = shift; } return $self->{$field}->{'VALUE'}; };
Whenever we call our instantiated class we pass through the closure for field access. When we call a field we first check it's 'ACCESS' entry in the hash to determine if access is 'private' or 'protected'. If no 'ACCESS' is defined or something else than 'private' or 'protected' we treat the field as public.
Checking a private field is easy. We already know that when caller(0) equals the package name __PACKAGE__ we are indeed calling from our class. If however caller(0) equals something else we're calling from outside.
Checking for protected fields is likewise the same. Whenever caller(0) IS A __PACKAGE__ and the field has 'protected' access we know we can access it. If caller(0) IS something else than __PACKAGE__ we give a run time error.
I want to avoid access to the fields in a direct way. My class Duck.pm provides easy setter methods setFlyBehaviour and setQuackBehaviour and I want the users of my class to use these setters all of the time.
(Imagine a setter that first checks the parameters and does some intermediate calculations before setting the value of the field. You really want to make sure your users call the setter to avoid having incorrect values in your field!)
The following code should be avoided as much as possible:
$whistle->("QUACKBEHAVIOUR", src::bo::behaviour::quack::CanQuack->new( +) );
A closure that implements checking of access does protect against setting fields directly.
As you can see in the following piece of code it's really simple to have private and/or protected access for your setters and/or getters.
sub setFlyBehaviour { caller(0)->isa(__PACKAGE__) || confess "setFlyBehaviour is protect +ed"; my $closure = shift; my $flyBehaviour = shift; &{ $closure }("FLYBEHAVIOUR", $flyBehaviour); } sub setQuackBehaviour { caller(0)->isa(__PACKAGE__) || confess "setQuackBehaviour is prote +cted"; my $closure = shift; my $quackBehaviour = shift; &{ $closure }("QUACKBEHAVIOUR", $quackBehaviour); }
The example of course gives only the code for protected access. If you want private access you should use for example the following line:
caller(0) eq __PACKAGE__ || confess "setQuackBehaviour is private" +;
private and protected fields, together with protected and public setters and getters give a lot of flexibility regarding the access to your fields.
The class Rubber defines the properties of a rubber Duck. A rubber Duck inherits of course all properties of a base Duck but also provides a method and field to set/get the color of the rubber Duck.
package src::bo::Rubber; use strict; use warnings; use Carp; use src::bo::Duck; use base qw/src::bo::Duck/; use src::bo::behaviour::fly::CannotFly; use src::bo::behaviour::quack::CanSqeek; sub new { my $class = shift; my $extends = $class->SUPER::new( @_ ); $extends->setFlyBehaviour( src::bo::behaviour::fly::CannotFly->new +() ); $extends->setQuackBehaviour( src::bo::behaviour::quack::CanSqeek-> +new() ); my $self = { COLOR => { 'VALUE' => undef, 'ACCESS' => 'protected' }, }; my $closure = sub { my $field = shift; if ( exists $self->{$field} ) { $self->{$field}->{'ACCESS'} eq 'private' and caller(0) eq __PACKAGE__ || confess "$field is private"; $self->{$field}->{'ACCESS'} eq 'protected' and caller(0)->isa(__PACKAGE__) || confess "$field is protected"; if ( @_ ) { $self->{$field}->{'VALUE'} = shift; } return $self->{$field}->{'VALUE'}; } else { return $extends->($field,@_); } }; bless ($closure, $class); return $closure; } sub setColor { my $closure = shift; my $color = shift; &{ $closure }("COLOR", $color ); } sub getColor { my $closure = shift; &{ $closure }("COLOR"); } 1;
Our constructor is of course new:
sub new { my $class = shift; my $extends = $class->SUPER::new( @_ ); $extends->setFlyBehaviour( src::bo::behaviour::fly::CannotFly->new +() ); $extends->setQuackBehaviour( src::bo::behaviour::quack::CanSqeek-> +new() ); my $self = { COLOR => { 'VALUE' => undef, 'ACCESS' => 'protected' }, }; my $closure = sub { my $field = shift; if ( exists $self->{$field} ) { $self->{$field}->{'ACCESS'} eq 'private' and caller(0) eq __PACKAGE__ || confess "$field is private"; $self->{$field}->{'ACCESS'} eq 'protected' and caller(0)->isa(__PACKAGE__) || confess "$field is protected"; if ( @_ ) { $self->{$field}->{'VALUE'} = shift; } return $self->{$field}->{'VALUE'}; } else { return $extends->($field,@_); } }; bless ($closure, $class); return $closure; }
First we start by calling the instructor of our base class by using the SUPER::new( @_ ) method. This call to SUPER::new( @_ ) gets us the closure of the base class Duck.pm.
By calling setFlyBehaviour and setQuackBehaviour we can give values to the FLYBEHAVIOUR and QUACKBEHAVIOUR fields in our base class.
For our COLOR field we define a hash COLOR in the normal way. Note that we use the same convention as in our base class. COLOR has a 'VALUE' and an 'ACCESS'.
Our closure in our subclass looks a lot like the closure from our base class. The only actual difference is the extra check to see if we're talking about a field defined in our own class or in the base class we're extending.
If we're talking about a field in our own class we provide the same checks as in the base class. If however it's a field from the base class we delegate all calls to the closure from the base class.
A little refactoring to provide this functionality in our base class would surely reduce our typing needs (but on the other hand would make this text less clear :(
to the authors of Objects with Private Variables and perltoot of course :)
|
---|