in reply to Objects with Private Variables

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";

Replies are listed 'Best First'.
Re: Re: Objects with Private Variables
by Excalibor (Pilgrim) on Jul 08, 2003 at 09:27 UTC

    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 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.


    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

      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.
      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?