Does Perl encapsulate a class variable?

I'm new to perl and I'm stuck in homework. I have an object with a class variable that counts the number of instances created. Then I have a subclass with an instance variable.

My first question is: how do I make a class variable hidden from the user? I tried using closure but couldn't figure out how to do inheritance with this. And the fact that this class variable made it worse, because the code that increments it was executed twice, and he said that I had two instances when I had one. It is not entirely clear why this happened, but it makes sense. I tried using scalars, but the variable did not increase correctly again. I have not tried the “inside out objects”, and I'm not sure what I want, it seems, over my head. I get the feeling that encapsulating class variables is different from encapsulating instance variables, but I can't find anything that explains how to do this.

My minor questions, as I mentioned, cannot make encapsulation work with inheritance. With closure, when you call the super constructor from a subclass, you get a link to the subroutine on the right, so there is no way (I know) to add instance variables to this.

Here is my base class:

#!/usr/bin/perl -w use strict; package Base; my $count = 1; sub new { my $class = shift; my $self = { _Count => $count # not hidden }; $count++; # increment count bless $self, $class; return $self; } sub Count { # getter my $self = shift; return $self->{_Count}; } 1; 

Here is my subclass:

 #!/usr/bin/perl -w use strict; package Sub; use Base; our @ISA = qw(Base); sub new { my $class = shift; my $self = $class->SUPER::New(); $self->{_Name} = undef; # not hidden return $self; } sub Name { #getter/setter my($self, $name) = @_; $self->{_Name} = $name if defined($name); return $self->{_Name}; } 1; 
+4
source share
5 answers

If you use empty Perl 5 (instead of using an OO structure), the usual way to execute class variables is lexical, visible only for access:

 { my $count = 0; sub Count { my ($self, $new_count) = @_; if (defined $new_count) { # NB only works if undef is not a legit value $count = $new_count; } return $count; } } 

$count is visible only in the closing block; even other methods in the same class do not see this. But anyone can manipulate it with $base_obj->Count or Base->Count , and any such manipulation will affect the shared variable.

You can also use closure to provide truly hidden instance variables. This is not worth doing if you are not following arbitrary homework rules.

 package Base; sub new { my ($class, $name) = @_; die "Need name!" unless defined $name; my $age; return bless sub { my ($attribute, @args) = @_; if ($attribute eq 'name') { if (@args) { die "Attempt to set read-only attribute!"; } return $name; } if ($attribute eq 'age') { if (@args) { ($age) = @args; } return $age; } die "Unknown attribute $attribute"; } => $class; } sub name { my ($self, @args) = @_; return $self->(name => @args); } sub age { my ($self, @args) = @_; return $self->(age => @args); } 

What happens is that the blessed sub returned by new closes over the two lexical words $name and $age . When new returns, these vocabulary goes beyond the scope, and the only access to them from this point forward is through closure. A closure can check its arguments to allow or deny access to the values ​​it has. As long as he never returns the link, he can be sure that he has the only direct access to these variables.

This also works with inheritance, without unnecessarily added subtlety:

 package Derived; use base 'Base'; sub new { my ($class, $name, $color) = @_; my $base_instance = $class->SUPER::new($name); return bless sub { my ($attribute, @args) = @_; if ($attribute eq 'color') { if (@args) { ($color) = @args; } return $color; } # base class handles anything we don't, possibly by dying return $base_instance->($attribute, @args); } => $class; } 

This emulates which languages ​​have different storage for instance data of the base and derived classes, either processing the request locally or passing it to the instance of the base class that was added to the closure. Deeper inheritance trees will lead to closure, closure closure, closure closure, each of which optionally also closes the instance variables needed by this particular class.

This is a pretty big mess for production and it’s very difficult to check and debug, and that’s why I’m going to emphasize again that you should never do this. But it’s very helpful to understand which end I am sending you to SICP .

+4
source

Like the local-local variable my , $count already hidden from users of the module / class. It seems that you are using the _Count instance _Count as a variable of the "current identifier" type, so that each object (instance) created gets a new identifier starting with 1. (If instead it is designed to track the number of active instances, then you need to reduce it to DESTROY , and there is no need to store a copy in the object.) If your test code creates only one instance, then its Count() method should return 1, but $count will be 2, since it started as 1 and was increased after saving the old value in object.

It is typical in perl to store instance variables in the $self hash, as you do without hiding them, although a prefix is ​​sometimes used to avoid collisions. They are protected more by convention (it is not safe to rely on implementation details because they can change) than language functions.

Take a look at the Moose module set if you want a higher level of control over perl classes.

+3
source

In Perl, fields are usually not hidden, applying this through the semantics of the language, but rather through a contract in the form of documentation. However, fields can be hidden using closures. It is also worth noting that Perl does not semantically distinguish between class methods and instance methods.

One of the standard ways to implement objects is with a blessed hash, just like you. This hash contains all instance variables / fields. Usually, "private" fields begin with underscores. Usually, the contract (documentation) will not indicate how these fields are stored, but requires that the class user go through various method calls.

Class variables should not be stored in an instance. It is better to use global variables or lexical variables. In the code you specified, $count is just a counter, but you never access it as a class variable. Instead, you assign a unique identifier to each instance. To use it as a class variable, provide the appropriate accessory (I removed unnecessary things like return s):

 { package Base; my $count = 0; sub new { my ($class) = @_; my $self = { ID => $count++, }; bless $self, $class; } sub Count { $count } sub ID { my ($self) = @_; $self->{ID} } sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." } } =head1 Base A generic base class =head2 Base->Count Return the object count. =head2 $base->ID Give the unique ID of this object. =head2 $base->report Returns a string containing a short description. =cut 

There is no business intervention with a counter in the subclass. This is ensured by the scope of the variable $count above, denoted by external curly braces. Subjects are closures on this variable.

 { package Sub; use parent -norequire, qw(Base); # remove `-norequire` if Base in different file sub new { my ($class) = @_; my $self = $class->SUPER::new; $self->{Name} = undef; $self; } sub Name :lvalue { my ($self) = @_; $self->{Name}; } sub report { my ($self) = @_; "I am the Sub object ".($self->ID)." called ".($self->Name)."."; } } =head1 Sub A generic subclass. It subclasses Base. =head2 $sub->Name [= SCALAR] Gets or sets the name of $sub. my $oldname = $sub->Name; $sub->name = "new name"; =cut 

As you can see, the Sub constructor calls the Base initializer and then adds a new field. It does not have class methods or class variables. The class does not have access to the $count variable, except for the accessor class method. The contract is indicated through the POD documentation.

(In the Name method, I use the annotation :lvalue , which allows me to simply assign the appropriate field in the object. However, this prohibits argument checking.)

Test

 my $base1 = Base->new; my $base2 = Base->new; print "There are now " . Base->Count . " Base objects\n"; my $sub1 = Sub->new; my $sub2 = Sub->new; print "There are now " . Base->Count . " Base objects\n"; $sub2->Name = "Fred"; print $_->report . "\n" for ($base1, $sub1, $base2, $sub2); 

prints

 There are now 2 Base objects There are now 4 Base objects I am the Base object 0. I am the Sub object 2 called . I am the Base object 1. I am the Sub object 3 called Fred. 

Pretty, right? (Except for the $sub1 , this object is missing its name.)

The documentation can be viewed with perldoc -F FILENAME and output something like

 Base A generic base class Base->Count Return the object count. $base->ID Give the unique ID of this object. $base->report Returns a string containing a short description. Sub A generic subclass. It subclasses Base. $sub->Name [= SCALAR] Gets or sets the name of $sub. my $oldname = $sub->Name; $sub->name = "new name"; 

only dials more nicely if you are on a * nix system.

Tested under v5.12.4.

Edit: Objects with an inner outline

While objects with the smallest externality provide the best coding, they are a bad idea: hard to understand, hard to debug, and hard to inherit, they provide more problems than solutions.

 { package Base; my $count = 0; sub new { bless \do{my $o = $count++}, shift } sub Count { $count } sub ID { ${+shift} } sub report { my ($self) = @_; "I am the Base object ".($self->ID)."." } } { package Sub; my @_obj = (); my $count = 0; sub new { my ($class) = @_; $count++; $_obj[$count - 1] = +{ parent => Base->new(), Name => undef, }; bless \do{my $o = $count - 1}, shift; } sub Name :lvalue { $_obj[${+shift}]{Name} } sub AUTOLOAD { my $self = shift; my $package = __PACKAGE__ . "::"; (my $meth = $AUTOLOAD) =~ s/^$package//; $_obj[$$self]{parent}->$meth(@_) } sub report { my ($self) = @_; "I am the Sub object ".($self->ID)." called ".($self->Name)."."; } } 

This implementation has the same interface and completes the test case with the same output. This solution is far from optimal, supports only one inheritance, does some intermediate things (autoload, dynamic method calls), but it works amazingly. Each object is actually a reference to an identifier that can be used to find the actual hash containing the fields. An array containing hashes is not accessible from the outside. The Base class has no fields, so an array of objects should not be created.

Edit2: Objects as coderefs

Another bad idea, but it's fun:

 { package Base; my $count = 0; sub new { my ($class) = @_; my $id = $count++; bless sub { my ($field) = @_; die "Undefined field name" unless defined $field; if ($field eq "ID") { return $id } else { die "Unrecognised name $field" } }, $class; } sub Count { $count } sub ID { my ($self) = @_; $self->("ID") } sub report { my ($self) = @_; "I am the Base object " . $self->ID . "." } } { package Sub; use parent -norequire, qw(Base); sub new { my ($class) = @_; my $name = undef; my $super = $class->SUPER::new; bless sub { my ($field, $val ) = @_; die "Undefined field name" unless defined $field; if ($field eq "Name") { defined $val ? $name = $val : $name } else { $super->(@_) } }, $class; } sub Name { my $self = shift; $self->("Name", @_) } sub report { my ($self) = @_; "I am the Sub object ".($self->ID)." called ".($self->Name)."."; } } 

The test case should be adapted to $sub2->Name("Fred") , and the corresponding documentation is updated accordingly, since we cannot safely use the lvalue annotation.

+3
source

To quote perldoc perlmodlib , "Perl does not use the private and public parts of its modules, as you may have used in other languages ​​such as C ++, Ada or Modula-17. Perl does not have privacy passions. Prefer you stay outside his living room because you weren’t invited, not because he has a shotgun. "

The standard convention in Perl is to put everything in the $self hash and use the underscore prefix to indicate which elements should be considered private ... and then trust the users of the class to follow this instruction. The same convention also applies to methods. If you use one of my modules, and you choose to peek under the covers and directly change the contents of $self or call $obj->_some_private_method , then you go to the forest and you can break something or what works fine in this version may break when updating until the next version; if this happens, you must save both parts.

If you intend to insist that data is not available to anyone outside the class itself, there are ways to do it, but a) they add complexity, which in almost all cases is not needed, and b) as you have already seen, they have the tendency to make inheritance much more difficult to work with.

So, my question for you is what are you really trying to accomplish, and why do you feel the need to make your Sooper-Sekret object data completely inaccessible? What benefits will you get by making it so that it’s not just provided by marking things that you think should be considered private and then trusting others to leave them alone (if they have no good reason for this)?

+3
source

First, I'm not quite sure what you mean by "hidden from the user", but it looks like you can look for variables with a scope of ( our ) against the instance.

 package MyBaseClass; use warnings; use strict; our $counter = 0; sub new { my $class = shift; $counter++; return bless {}, $class; } sub howManyInstances { return $counter; } 1; 

On the second question, I'm not sure if closure is related to inheritance. Here's a simple subclass:

 package MySubClass; use warnings; use strict; use parent 'MyBaseClass'; # use parent schema, don't mess with @ISA sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{_name} = undef; return $self; } # Your setter/getter looks ok as is, though lowercase is tradional for methods/subs 1; 

Now, if it were real code, you would not do it - you would use Moo or Moose .

+2
source

All Articles