Perl OOP attribute management best practices

Assume the following code:

package Thing; sub new { my $this=shift; bless {@_},$this; } sub name { my $this=shift; if (@_) { $this->{_name}=shift; } return $this->{_name}; } 

Now suppose we created an object this way:

 my $o=Thing->new(); $o->name('Harold'); 

Good. We could also quickly create the same thing: either

 my $o=Thing->new(_name=>'Harold'); # poor form my $o=Thing->new()->name('Harold'); 

Of course, I allowed the attributes to be passed in the constructor to allow the "friendly" classes to create objects more fully. It can also allow an operator of type clone with the following code:

 my $o=Thing->new(%$otherthing); # will clone attrs if not deeper than 1 level 

This is good and good. I understand the need to hide attributes behind methods that allow validation, etc.

 $o->name; # returns 'Harold' $o->name('Fred'); # sets name to 'Fred' and returns 'Fred' 

But this does not make it easy to manipulate an attribute based on it, for example:

 $o->{_name}=~s/old/ry/; # name is now 'Harry', but this "exposes" the attribute 

One option is as follows:

 # Cumbersome, not syntactically sweet my $n=$o->name; $n=~s/old/ry/; $o->name($n); 

Another potential is the following method:

 sub Name :lvalue { # note the capital 'N', not the same as name my $this=shift; return $this->{_name}; } 

Now I can do the following:

 $o->Name=~s/old/ry/; 

So my question is ... is it above "kosher"? Or is it a bad form to expose an attribute this way? I mean, this removes any validation that can be found in the name method. For example, if the method “name” forcibly put the first letter and a lower case letter into it, “Name” (capital “N”) bypasses this and forces the user of this class to protect themselves when using it.

So, if the “Name” lvalue method is not exactly “kosher”, are there any established ways to do such things?

I considered (but I am dizzy considering) things as attached scalars, as attributes. Of course, this could be the way to go.

Also, maybe there might be overloads that might help?

Or should I create replacement methods in the spirit (if it works):

 sub replace_name { my $this=shift; my $repl=shift; my $new=shift; $this->{_name}=~s/$repl/$new/; } ... $o->replace_name(qr/old/,'ry'); 

Thanks in advance ... and mind you, I'm not very good at the Perl OOP brand, although I'm pretty good at OOP.

Additional info: I guess I could get very creative with my interface ... here is an idea I was messing around with, but I think it shows that there really are no boundaries:

 sub name { my $this=shift; if (@_) { my $first=shift; if (ref($first) eq 'Regexp') { my $second=shift; $this->{_name}=~s/$first/$second/; } else { $this->{_name}=$first; } } return $this->{_name}; } 

Now I can either set the name attribute with

 $o->name('Fred'); 

or i can manipulate it with

 $o->name(qr/old/,'ry'); # name is now Harry 

This still doesn't allow things like $ o-> name. = 'Jr.'; but it’s not too difficult to add. Heck, I could let pass calllback functions, right?

+7
oop perl
source share
4 answers

Your first code example is absolutely right. This is the standard way to write accessories. Of course, this can become ugly when doing the substitution, the best solution might be:

 $o->name($o->name =~ s/old/ry/r); 

The /r flag returns the result of the substitution. Equivalent:

 $o->name(do { (my $t = $o->name) =~ s/old/ry/; $t }); 

Well yes, this second solution is admittedly ugly. But I assume that accessing the fields is a more common operation than setting them up.

Depending on your personal preferences, you can have two different ways of getting and setting, for example. name and set_name . (I don't think get_ prefixes are a good idea - 4 unnecessary characters).

If substituting parts of a name is a central aspect of your class, then encapsulating it in a special substitute_name method sounds like a good idea. Otherwise, it’s just an extra ballast and a bad compromise to avoid accidental syntax pain.

I do not advise you to use lvalue methods, as these are experimental .

I would rather not see (and debug) some “smart” code that returns anchored scalars. It will work, but for me it is becoming too fragile to be convenient with such solutions.

Overloading the operator does not help when recording accessories. A special purpose cannot be overloaded in Perl.


Writing accessories is boring, especially if they don’t check. There are modules that can handle auto-generation for us, for example. Class :: Accessor . This adds the common get and set access classes to your class, as well as special accessors on request. For example.

 package Thing; use Class::Accessor 'antlers'; # use the Moose-ish syntax has name => (is => 'rw'); # declare a read-write attribute # new is autogenerated. Achtung: this takes a hashref 

Then:

 Thing->new({ name => 'Harold'}); # or Thing->new->name('Harold'); # or any of the other permutations. 

If you need a modern Perl object system, there are a number of compatible implementations. The most functional of them are Moose and allows you to add checks, constraint types, default values, etc. to your attributes. For example.

 package Thing; use Moose; # this is now a Moose class has first_name => ( is => 'rw', isa => 'Str', required => 1, # must be given in constructor trigger => \&_update_name, # run this sub after attribute is set ); has last_name => ( is => 'rw', isa => 'Str', required => 1, # must be given in constructor trigger => \&_update_name, ); has name => ( is => 'ro', # readonly writer => '_set_name', # but private setter ); sub _update_name { my $self = shift; $self->_set_name(join ' ', $self->first_name, $self->last_name); } # accessors are normal Moose methods, which we can modify before first_name => sub { my $self = shift; if (@_ and $_[0] !~ /^\pU/) { Carp::croak "First name must begin with uppercase letter"; } }; 
+4
source share

The purpose of the class interface is to prevent users from directly manipulating your data. What you want to do is cool, but not a good idea.

In fact, I design my classes, so even the class itself does not know its own structure:

 package Thingy; sub new { my $class = shift; my $name = shift; my $self = {}; bless, $self, $class; $self->name($name); return $self; } sub name { my $self = shift; my $name = shift; my $attribute = "GLUNKENSPEC"; if ( defined $name ) { $self->{$attribute} = $name; } return $self->{$attribute}; } 

You can see my new constructor so that I can pass it a name for my Thingy. However, my constructor does not know how I store my name. Instead, it simply uses my name method to set the name. As you can see in my name method, it saves that name in an unusual way, but my constructor should not know or care.

If you want to manipulate a name, you must work on it (as you showed):

 my $name = $thingy->name; $name =~ s/old/ry/; $thingy->name( $name ); 

In fact, many Perl developers use internal classes to prevent this direct manipulation of objects.

What if you want to be able to directly manipulate the class by passing in a regular expression? You should write a way for this:

  sub mod_name { my $self = shift; my $pattern = shift; my $replacement = shift; if ( not defined $replacement ) { croak qq(Some basic error checking: Need pattern and replacement string); } my $name = $self->name; # Using my name method for my class if ( not defined $name ) { croak qq(Cannot modify name: Name is not yet set.); } $name = s/$pattern/$replacement/; return $self->name($name); } 

Now the developer can do this:

 my $thingy->new( "Harold" ); $thingy->mod_name( "old", "new" ); say $thingy->name; # Says "Harry" 

No matter what time or effort that you save, allowing direct manipulation of objects, is offset by the amount of extra effort that will be required to support your program. Most methods do not take more than a few minutes. If I suddenly wanted to manipulate my object in a new and amazing way, it's easy enough to create a new method for this.


1. No. In fact, I do not use random meaningless words to protect my class. This is just for demonstration purposes, to show that even my constructor does not need to know how the methods actually store their data.

+3
source share

I understand the need to hide attributes behind methods that allow validation, etc.

Validation is not the only reason, although it is the only one you are talking about. I mention this because the other is that encapsulation, like this, leaves the implementation open. For example, if you have a class that must have the string "name" that you can get and set, you can simply open the member name. However, if you use get () / set () routines instead, how the "name" is stored and displayed inside does not matter.

This can be very important if you write code bundles using a class, and then you suddenly realize that although the user can access the "name" as a string, it will be much better saved in some other way (for some reason), If the user directly accessed the line directly as a member field, now you must either compensate for this by including a code that will change the name when the real everything changes and ... but wait, how can you compensate the code for the client that changed name...

You can not. You are stuck. Now you need to go back and change all the code that the class uses, if possible. I am sure that anyone who has done enough OOP has encountered this situation in one form or another.

Sure, you've already read all this, but I am returning it again, because there are a few points (maybe I misunderstood you) where you seem to outline strategies for changing the "name" based on your knowledge of the implementation, and not what there had to be an API. This is very tempting in perl because of course there is no access control - everything is important to the public, but it is still very bad practice for the reason just described.

This does not mean, of course, that you cannot just commit the "name" as a string. This is a decision, and it will not be the same in all cases. However, in this particular case, if you are particularly interested in simply converting the "name", IMO, you can also use the get / set method. It:

 # Cumbersome, not syntactically sweet 

It may be true (although someone may say that it is simple and understandable), but your main concern should not be syntactic sweetness, and there should not be speed of execution. They may be fears, but your main concern should be constructive, because no matter how sweet and fast your things are, if they are poorly designed, all of this will come down with you on time.

Remember, “Premature optimization is the root of all evil” ( Knuth ).

+2
source share

So my question is ... is it above "kosher"? Or is it a bad form to expose an attribute this way?

It boils down to: will it continue to work if the internal changes change? If so, you can do many other things, including but not limited to validation.)

The answer is yes. This can be done if the method returns a magic value.

 { package Lvalue; sub TIESCALAR { my $class = shift; bless({ @_ }, $class) } sub FETCH { my $self = shift; my $m = $self->{getter}; $self->{obj}->$m(@_) } sub STORE { my $self = shift; my $m = $self->{setter}; $self->{obj}->$m(@_) } } sub new { my $class = shift; bless({}, $class) } sub get_name { my ($self) = @_; return $self->{_name}; } sub set_name { my ($self, $val) = @_; die "Invalid name" if !length($val); $self->{_name} = $val; } sub name :lvalue { my ($self) = @_; tie my $rv, 'Lvalue', obj=>$self, getter=>'get_name', setter=>'set_name'; return $rv; } my $o = __PACKAGE__->new(); $o->name = 'abc'; print $o->name, "\n"; # abc $o->name = ''; # Invalid name 
+2
source share

All Articles