Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Objects with Private Variables

by btrott (Parson)
on Apr 20, 2000 at 23:26 UTC ( #8251=perltutorial: print w/ replies, xml ) Need Help??

Objects with Private Variables

Designed to answer the question: how can I give my object "private" variables?

Introduction

Many perl objects are implemented as hash references, which are inherently unprivate. Developers from some other, more highly-private languages often find this unsatisfactory, particularly since Perl is "interpreted" and not compiled into binaries--after all, if anyone can look at your source and see what variables you're using internally in your object, and then they can access those variables, isn't that insecure/dangerous? :)

Anyway, implemented as hash references, it's difficult to really protect your data. But if you implement your object as a closure, you can protect your data as much as you'd like! This method is described in perltoot, and it's pretty neat.

Implementation

The Constructor

You start by declaring your object normally:
package Person; use strict; sub new { my $type = shift; my $class = ref $type || $type; my $self = { NAME => undef, AGE => undef };
Up until here, everything looks normal: you've declared a package "Person", and you've started to create a constructor; you determine the class that this object will be bless'ed into; and you set up the data fields. A person will have a name and an age.

If we were implementing our object as a hash reference, we'd bless $self into $class and be done with it. But that would let any user of our object mess with NAME and AGE directly, rather than through our accessor methods.

So, instead of a hash reference, we implement the object as a closure. A closure is a subroutine reference that has access to the lexical variables that were in scope when it was created. So, in this case, we're most concerned with our closure being able to access the fields in $self.

The closure will be an anonymous subroutine that will act as an accessor/setter method. We'll give it a field name (NAME or AGE), and a possible second argument (a value to set that field to), and it will return the value of the field. So let's implement that:

my $closure = sub { my $field = shift; @_ and $self->{$field} = shift; $self->{$field}; };
Pretty simple: the closure takes a field, sets the field if it's given a value, and returns the value either way.

Now the magic: we're going to bless our closure into $class so that the closure *is* the object!

bless $closure, $class; $closure; }
We create a new closure object, blessing it into $class, and return it; and we're done.

Accessor Methods

Now we just need to write our accessor methods. We need one of these for each data fields; they'll be the interface to our object. They'll receive the object (the closure) as their zeroth argument, and then any additional information (such as the value to set the field to) as additional arguments:
sub name { &{ $_[0] }("NAME", @_[1 .. $#_ ] ) } sub age { &{ $_[0] }("AGE", @_[1 .. $#_ ] ) }
As mentioned, these receive the closure (a code reference) as the zeroth argument, so they invoke that code reference. The arguments they provide are the field name ("NAME" or "AGE"), and then a list of any other arguments that were handed to them.

Thus, what happens is that the closure (which, remember, has access to our data fields via $self) gets the name of the field, sets the value of the field if a value is provided, then returns the value.

Usage

You can use this object just as you would any other. Here's a short example:
use strict; use Person; my $person = new Person; $person->name("Foo Bar"); $person->age(22); print $person->name, " is ", $person->age, " years old.\n";

What's Neat About This

From a user's perspective, this looks and acts just like a "normal" (ie. hash reference) object. But the user has no way at all of accessing or modifying your data fields unless you've defined an accessor method.

For example, you could define a new data field (called "PRIVATE", for example)--and if you didn't define an accessor method, the user would have no way of accessing that field. In your implementation, however, you could access it as much as you wished (although you'd have to go through the closure to do so).

Which, I think, is pretty neat.

See Also

This tutorial is based on the example given in Tom Christiansen's perltoot, so look there for his explanation and some other fun tricks.

Comment on Objects with Private Variables
Select or Download Code
Re: Objects with Private Variables
by Anonymous Monk on Mar 30, 2003 at 16:21 UTC
    So how does the closure know what $self it is using? As far as I understand, $self will be out of scope when I return from "new". Or is a new reference to the "new" block saved each time it runs?

      You missed the key point - the anonymous subroutine is a closure because it "captures" the $type, $class and $self instances. So when you call the function later the $self variable is still accessible from inside $closure thus it all works.

Re: Objects with Private Variables
by runrig (Abbot) on Mar 30, 2003 at 17:26 UTC
    But the user has no way at all of accessing or modifying your data fields unless you've defined an accessor method.
    Not quite. You can still go "under the hood" from outside of the module:
    my $person = new Person; $person->name("Foo Bar"); $person->age(22); # No 'toes' method $person->("toes", 12); print $person->name, " is ", $person->age, " years old.\n"; print "And has ", $person->("toes"), " toes\n";

      Indeed. One way to avoid this is to make the methods available for just the attributes we have:

      package Person; { my @attributes = (qw/NAME AGE/); sub new { my $type = shift; my $class = ref($type) || $type; my %self = map { $_ => undef } @attributes; my $access = sub { my ($name, $arg) = @_; use Carp; croak "Method '$name' unknown for class Person" unless exists $self{$name}; $arg and $self{$name} = $arg; $self{$name}; }; #### convenience methods are denined here... for my $method ( keys %self ) { no strict 'refs'; *$method = sub { my $self = shift; $access->( $method, @_ ); }; } bless $access, $class; return $access; } } sub salute { my $self = shift; print "Hello, I'm ", $self->NAME, " and I'm ", $self->AGE, "!\n"; }

      This will make it enough. We make a lexical of the attributes we need, then wrap it inside the constructor (the closure) so it's remembered when it goes out of scope. We also create setters/getters on the flight, a beautiful ability if not abused.

      We also make a nifty trick I learned from Master Damian Conway, by making it lexical, the rest of the class methods (salute(), but actually all of them defined outside the constructor) must use the setter/getters (or the interface) defined inside the lexical scope. Effectively, there's no way for salute() to access the state of the object but by asking it through the methods available.

      Let's put this to work:

      package main; my $person = new Person; # nice interface $person->NAME( 'John Doe' ); $person->AGE( 23 ); $person->salute; # this also works, of course $person->('AGE', 24); $person->salute; # this doesn't $person->('TOES', 12);

      The output from this is, as expected:

      Hello, I'm John Doe and I'm 23! Hello, I'm John Doe and I'm 24! Method 'TOES' unknown for class Person at Person.pl line 59

      The initialization and setters worked OK, and then salute() worked as expected. When we tried to trick the object with another method, by adding to the supposed table of methods, it didn't work, because we explicitely know which attributes we manage (in the normal approach, you can never know). This approach makes lifes easier to maintain (just add a new attribute to the array!) and allows us to be very paranoid about access... and by being a lexical, only the code inside the scope of @attributes can change it, but it never does, and so you cannot trick the object. Of course, there's always a way, you can work on perl guts and add a new attribute to the *attributes table, maybe.

      Anyway the example was a nice one, point well taken.

      laters,
      david

      --
      our $Perl6 is Fantastic;

        Looking good David!

        The only thing I would change is to let $self be the ref to the closure and use another name for the actual data storage. This way you always know that $self is the ref to your object. For instance if you want to call a couple of private methods in your constructor after blessing your variable it would be more staightforward to do
        $self->_privateMethod();
        than
        $access->_privateMethod();

        Cheers,
        Rob
        Nice, but if you want your class methods to use the class variables, you have to use the getters and setters as well.
        I now use your code adjusted like this (also taken the point of misterb101.
        package person; use strict; use Carp qw(croak cluk); { my @attributes = (qw/NAME AGE/); sub new { my $type = shift; my $class = ref $type || $type; DEBUG => 1, # Values 0, 1, 2 (0ff, self, include chi +lds) @_ }; my %DataStorage = map { $_ => undef } @attributes; my $self = sub { my ($name, $arg) = @_; #internal setter is able to create variables and getter-se +tter my @caller = caller; if (!exists $DataStorage{$name} && $caller[0] eq $DataSto +rage{CLASSNAME}) { $DataStorage{$name} = $arg; } croak "Method '$name' unknown for class $DataStorage{CLASS +NAME}" unless exists $DataStorage{$name}; $arg and $DataStorage{$name} = $arg; $DataStorage{$name}; }; #### convenience methods are denined here... for my $method ( keys %DataStorage ) { no strict 'refs'; *$method = sub { my $self = shift; $self->( $method, @_ ); }; } bless $self, $class; $DataStorage{DEBUG} = $arg->{DEBUG}; return $self; } } sub salute { my $self = shift; print "Hello, I'm ", $self->NAME, " and I'm ", $self->AGE, "!\n"; # and i'm allowed to set my own object-variable "toes" $self->("TOES",5); print "I've got " . $self->("TOES") . " toes on each foot."; } 1;
        "We all agree on the necessity of compromise. We just can't agree on when it's necessary to compromise." - Larry Wall.
Re: Objects with Private Variables
by zby (Vicar) on Mar 27, 2008 at 10:34 UTC
    I think this node is made obsolete by Moose.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perltutorial [id://8251]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (7)
As of 2014-09-19 00:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (128 votes), past polls