Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Re: Re: Objects with Private Variables

by Excalibor (Pilgrim)
on Jul 08, 2003 at 09:27 UTC ( [id://272227]=note: print w/replies, xml ) Need Help??


in reply to Re: Objects with Private Variables
in thread Objects with Private Variables

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;

Replies are listed 'Best First'.
Re^3: Objects with Private Variables
by misterb101 (Sexton) on Jun 07, 2005 at 09:36 UTC
    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
Re^3: Objects with Private Variables
by jbrugger (Parson) on Jun 08, 2005 at 07:10 UTC
    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^3: Objects with Private Variables
by akr8986 (Initiate) on Dec 18, 2015 at 11:37 UTC
    What if i wanted to to have both private and public variables inside a perl module? I cant return a hash with closure reference as one of its members as the object? if i do that then the user can call the closure subroutine directly and access the variables.. lemme start with a real time example.. say i have 3 variables a,b,c and a STATE variable. The user can manipulate a,b,c but the state will be decided by the class itself. How can i implement this class?

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (6)
As of 2024-03-29 11:43 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found