Back

Writing powerful accessors for Perl classes

In most object-oriented programming languages writing accessor functions for internal properties is a dull and error-prone exercise. This is not the case with Perl if the modules Class::Accessor or Class::Accessor::Grouped are used.

Accessor functions have a special role in Perl. Different to other object oriented programming languages, objects in Perl are very often stored in hash structures and the values of these structures can be accessed and manipulated from anywhere in the code. In that way Perl objects are very similar to JavaScript objects.

While in fully object oriented languages, accessor functions are often the only way to access an object's "private" properties, in Perl these functions provide a convenient way to assure that the correct values end up in the right places. Perl is also able to detect spelling errors when an accessor should be used because these accessors are functions. This is not the case for hashed data, which stores the data associated to names. A spelling error will just lead to another value in the data structure and therefore, such errors are very difficult to identify and to fix. Accessor functions avoid such problems because if a function name is spelled wrong, Perl will not recognise it and throw a meaningful error.

A common practice in Perl is to have integrated accessors. These accessors will set or get the value of an internal property depending if a argument is passed to the function or not. Such integrated accessors frequently result in if-then-else clauses. The related code caused a lot of headaches to Perl coders in the past, because it attracts errors to lurk into the code. This is a really bad thing for a language that has its strengths in rapid prototyping. The two classes Class::Accessor and Class::Accessor::Grouped solve these problems and bring programmers up to speed for meeting deadlines.

Class::Accessor

Class::Accessor simplifies the programming of accessor functions to the following statements in a class definition.

package My::Person;
use base qw(Class::Accessor);
__PACKAGE__->mk_accessors(qw(name telephone_number));
1;

The first statement is the name of the class that is about to be created. In this case this is My::Person.

The second statement sets Class::Accessor as a base class for the My::Person class. This is needed for creating the accessor functions.

The third statement is the actual definition of two accessor functions, namely "name" and "telephone number". The token PACKAGE takes care that the accessor functions are associated to the correct class. This statement could have been also written as

My::Package->mk_accessors(qw(name telephone_number));

The PACKAGE token is in many cases more convenient and faster to type (but this is a different story).

The last statement is the Perl specific package terminator.

With the given class definition it is possible to write the following code in your programs.

my $person = My::Person->new();
$person->name('Christian Glahn');
if ( $person->name ne 'Christian Glahn' ) {
    die "This is not the correct name";
}

Alternatively, one can also use special getter and setter functions to manipulate the code. In this case the program would look like this.

my $person = My::Person->new();
$person->set_name('Christian Glahn');
if ( $person->get_name ne 'Christian Glahn' ) {
    die "This is not the correct name";
}

This second variant is useful if the simple accessor should be extended with some validation function in the base class. Because of the implementation of Class::Accessor one cannot write the following code in the class definition.

# WRONG
sub name {
    my $self = shift;
    if ( scalar @_ ) {
        if ( $self->validate_name(@_) ) {
            $self->SUPER::name(@_);
        }
    }
    return $self->SUPER::name;
}

Because the accessor functions are not part of Class::Accessor, but of the issuing class (in this case My::Person), Perl's SUPER statement cannot be used. Instead the code has to be written as following.

# CORRECT
sub name {
    my $self = shift;
    if ( scalar @_ ) {
        if ( $self->validate_name(@_) ) {
            $self->set_name(@_);
        }
    }
    return $self->get_name;
}

This is type of code is OK, if only one or two accessors in a class require special treatment while many other accessors don't. Another limitation of Class::Accessor is that it can only be used to define accessors for instances of a class, but not accessors for properties that are shared by all instances of a class.

Class::Accessor::Grouped

If one needs more power and control over the accessors, Class::Accessor::Grouped provides a far more flexible interface than Class::Accessor. Therefore, this class can be considered as the big brother of Class::Accessor. Class::Accessor::Grouped allows to define groups of accessors that share similar storage, validation and retrieval schemes. The grouping into different schemes also allows to use accessors of different types in parallel.

Defining group accessors is as simple as with Class::Accessor. The only difference is that with Class::Accessor::Grouped one has to add the group's name for the accessors to be defined. We can therefore write a simple class for people in an hierarchical organization.

package My::OrgPerson
use base qw(Class::Accessor::Grouped);
__PACKAGE__->mk_group_accessors('simple', qw(name telephone_number));
__PACKAGE__->mk_group_accessors('inherited', qw(hierarchy));
1;

If we subclass My::OrgPerson we can write the following code.

package My::OrgPerson::Manager;
use parent qw(My::OrgPerson);
__PACKAGE__->hierarchy('manager');
1;

Now all instances of My::OrgPerson::Manager will share the value 'manager' in their hierarchy accessor.

As this example illustrates, Class::Accessor::Grouped comes with two predefined groups of accessors: "simple" and "inherited".

The "simple" group provides the same functions as Class::Accessor. The main difference between the two implementations is that the accessors of the group "simple" will throw meaningful errors much earlier in the process if they are applied on invalid objects.

More interesting is the "inherited" group. This group allows to define accessors for properties that are shared among all instances of a class (which are sometimes also called 'static properties').

However, the real power of grouping accessors comes with creating new groups. Defining a new group is very simple. One has only to write a generic getter and a generic setter function for the group. This way it is possoble to write a more generic version of the validating accessor at the end of the previous section.

package My::OrgPerson;
use base qw(Class::Accessor::Grouped);
__PACKAGE__->mk_group_accessors('validate', qw(name telephone_number));
__PACKAGE__->mk_group_accessors('inherited', qw(hierarchy));

sub set_validate {
    my ($self, $field, $value) = @_;
    my $validatefunc = "validate_$field";
    if ( $self->$validatefunc($field, $value) ) {
        $self->set_simple($field, $value);
    }
}
sub get_validate {
    my ($self, $field) = @_;
    return $self->get_simple($field);
}
# ...

This code will only store values in the data structure that are valid for the given type. Although one has still to program the validation functions, this code simplifies the creation of accessors for validated values. Another interesting aspect of this example is that instead of accessing the data structure directly, it reuses Class::Accessor::Grouped's simple getter and setter functions. Alternatively the example could have used the inherited getter and setter functions, if the validated accessors should be available across the instances of a class.

Conclusions

Class::Accessor and Class::Accessor::Grouped are powerful Perl modules for simplifying class definitions. This automatically reduces possible errors and helps developers to achieve high-quality solutions much quicker.

For simple tasks Class::Accessor is a fast and relyable solution. If one is in demand of more control and/or requires more complex logic in storage procedures of the accessors, Class::Accessor::Grouped offers the by far more flexible interfaces.