Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

radiantmatrix's scratchpad

by radiantmatrix (Parson)
on Aug 19, 2004 at 15:12 UTC ( [id://384323]=scratchpad: print w/replies, xml ) Need Help??


In a comment below is a nice, calming Blue theme.

This document is a primer guide to using inside-out objects via Class::InsideOut. It is intended as a compliment to, not a replacement of, the documentation provided with that manual.

Who should read this primer?

This primer intends to address Perl programmers who are already familiar with using and creating object classes in Perl, and who wish to learn about and implement the inside-out class pattern using the Class::InsideOut module.

Introduction to the inside-out class pattern

Anyone familiar with object class development in Perl is familiar with the traditional pattern for a Perl object class: a single, blessed hash reference. Each object instance is its own hash reference with the same data structure.

This approach is simple, and it works well; however, it has certain limitations. For example, anyone instantiating an object can bypass the accessors you so carefully constructed and muck about with the data in the object – after all, the object is just a hash reference like any other.

Besides that, how often have you accidentally wound up with a hard-to-find bug in your object class because you've mistyped the name of a hash key? The interpreter certainly doesn't warn you: it just auto-vivifies a new attribute with the mistyped name.

The inside-out class pattern addresses these issues (and some other more esoteric ones) by figuratively turning the idea of a class “inside-out”. Whereas a traditional object instance is a blessed reference to a hash whose keys are the names of attributes, an inside-out object instance is a common key to several class-local hashes that represent the attributes. That is, an object class defines a hash for each attribute, and an instance is the key that points to one element in that hash. By way of example:

# Accessing an attribute inside a traditional object $self->{ attribute } = $value; # Accessing an attribute inside an inside-out object $attribute { id $self } = $value;

This may be a little hard to wrap your head around at first, but as you continue this primer, it will become more apparent.

Building your first inside-out class

When building any class, one has to first decide what attributes need to be present, and what methods will act on those attributes. For an inside-out class, we'll also need to decide which of the attributes are public, read-only, or private.

A public attribute can be both read and modified by any program or module that instantiates the object (any instantiator.

A read-only attribute can be read by any instantiator, but modified only internally by the class (and its children, which we'll get to later).

A private attribute is not available to the instantiator, only to the class itself (an, again, it's children).

Let's create a class to represent a person in an organization. We'll need to know the person's given name, family name, date of birth, phone number, Social Security Number, and position within the organization. Some of these attributes shouldn't be changed by the instantiator, and Social Security Number should be kept private – it's only used internally as a unique identifier.

Now, let's look at the same class, declared with the inside-out pattern and using Class::InsideOut:

package My::Person; use Class::InsideOut qw[public readonly private register id]; # declare the attributes readonly given_name => my %given_name; readonly family_name => my %family_name; public birthday => my %birthday; public phone => my %phone; private ssn => my %ssn; public position => my %position; # object constructor sub new { my $self = shift; register( $self ); $given_name{ id $self } = shift; $family_name{ id $self } = shift; $birthday{ id $self } = shift; $phone{ id $self } = shift; $ssn{ id $self } = shift; $position{ id $self } = shift; }

The first item of interest is that the attributes are declared in the class' package scope, rather than per-instance. Notice that attribute setting/getting inside methods appears “inside-out”.

Using an inside-out class

From the instantiators' point of view, using an inside-out class is no different than using a regular class:

use My::Person; my $manager = My::Person->new( 'Random', 'Hacker', '12/18/1987', '555-1212', 'CEO' ); print "Old phone is: ",$manager->phone(); $manager->phone('555-1313'); #set new phone number print "New phone is: ",$manager->phone();

Note that by declaring the attribute 'phone' as public, Class::InsideOut automatically creates a method that works as an accessor when called without parameters, and as a mutator when called with a parameter.

The major difference instatiators will notice is that the following code will not work in an inside-out class (it would in a traditional class):

$manager->{phone} = '555-1313';

{{what is the behavior of this??}}

Defining accessors and mutators

As we've seen above, there's no need to explicitly define accessors and mutators, because Class::InsideOut does it for you. The syntax for this definition is:

protection accessor_name => my %attribute_variable;

An accessor method will be created for any public or readonly attribute; in the case of a public attribute, the accessor will function as a mutator when it is passed a parameter. For example:

# $obj->public_attrib() accessor, $obj->public_attrib($value) mutator public public_attrib => my %public_attrib; # $obj->read_attrib() accessor, no mutator readonly read_attrib => my %read_attrib; # no accessor or mutator private priv_attrib => my %priv_attrib;

Notice that throughout this document, the accessor name and the the name of the attribute variable are the same. While this is not strictly required, it makes things so much clearer that it's considered a best practice.

Defining other types of methods

Creating methods that act on data inside an object is not entirely dissimilar from the traditional class pattern approach:

sub sendPhoneToDirectory { # sends the phone number to the company directory, using SSN as # the key my $self = shift; my $dir = My::Directory->new(); my $entry = $dir->getEngtryBySSN( $ssn{ id $self } ); $entry->phone( $phone{ id $self } ); $entry->commit; }

The only real difference is how attributes are addressed. Instead of using '$self' as a hash reference and the attribute name as a key, we use the attribute hash and the 'id' of '$self' (not the object itself, but its unique identifier) as the key.

Advanced accessor and mutator behavior

While Class::InsideOut's ability to automatically generate the accessors/mutators for read-only and public attributes is certainly convenient, it does raise a fairly obvious question: what happens if I want my accessor/mutator to do a little more than just get or set the value of the attribute?

It's generally not advisable to have excessive amounts of logic around getting or setting a value, but there are plenty of places where a little bit of logic is appropriate. The most obvious example for a mutator is the desire to validate the proposed value before setting the attribute. For instance, it wouldn't do to allow a floating-point value to be assigned to an attribute that's supposed to be an integer. For accessors, the most obvious example would be dereferencing a data structure before returning it.

Fortunately, Class::InsideOut has this facility. When declaring attributes, two hooks are available. The first, for hooking into the accessor behavior, is called 'get_hook'; its companion, 'set_hook', hooks into mutator behavior, when that's relevant.

These hooks are code references that will be executed either before setting or after getting the attribute, but before the accessor or mutator returns. The return value is ignored, and the '$_' variable is locally aliased so that it contains the value in the attribute (in the case of an accessor) or the value to be assigned (in the case of a mutator).

An attribute can have either a set_hook or a get_hook, or both.

Building a set_hook

A set_hook is a subroutine used to validate or modify the value passed to an attribute mutator before setting the attribute. Inside a set_hook, the variable '$_' is set up to be the value passed to the mutator. Return values are ignored – this means that one changes '$_' if one wants to modify the passed value, and dies if one wants to fail (e.g. if bad data were passed to the mutator).

By way of example, imagine that our My::Person class wants to store the birthday as an integer similar to what would be returned by Perl's 'time' function. However, we want to accept MM/DD/YYYY as input. We'll use the POSIX mktime() function to actually make the conversion. Because this is an example, we'll be assuming that anything that looks like a date, is; in a production environment, you'd want your validation to be much more robust. Here's the sub:

sub _set_birthday { # remember, $_ will contain the value passed to the mutator die "$_ is not a valid date" unless m{(\d{2})/(\d{2})/(\d{4})}; $_ = mktime(0,0,0,$2-1,$1-1,$3-1900); #pack it! }

Now, we have to register this subroutine with the set_hook for the 'phone' attribute. This is done at declaration:

public birthday => my %birthday, { set_hook =>; \&_set_birthday };

Now, any time an instantiator calls the 'birthy' mutator, the value will be converted. If the value doesn't look like a date, the mutator dies. Note that any code reference will be executed, so there's no need to use a named sub: the 'set_hook' could just as easily be an anonymous sub.

Building a get_hook

A get_hook is a subroutine used to modify the value returned by an attribute accessor. Inside a get_hook, the variable '$_' is set up to be the value stored in the attribute. Return values from get_hook are ignored; the accessor expects that get_hook will simply modify '$_'.

Let's continue our birthday example from the 'set_hook' discussion. Recall that the birthday is stored as a time code in the same form as would be generated by Perl's 'time' function. However, since we accept MM/DD/YYYY as input to the mutator, we should provide the same format as output from the accessor. We'll use the POSIX strftime() function to do the heavy lifting. Here's the sub:

sub _get_birthday { # remember, $_ will contain the value of the attribute $_ = strftime '%m/%d/%Y', localtime( $_ ); }

As with a 'set_hook', we register the get_hook at declaration time. Here's what the declaration looks like after adding the get_hook:

public birthday => my %birthday, { set_hook => \&_set_birthday, # was already here get_hook => \&_get_birthday, };

Now, when the birthday accessor is called, it will return the date in the MM/DD/YYYY format. As with set_hook, an anonymous sub could be used instead of a separate, named sub.

Further reading

Hopefully, this text has been helpful in getting you started creating your own inside-out modules using Class::InsideOut. It probably won't be long before you find yourself wanting to do more advanced things using inside-out objects, like serialization, inheriting from traditional classes, and writing thread-safe modules. To address these topics, please refer to the excellent Class::InsideOut::Manual::Advanced.

See also



Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (6)
As of 2024-03-28 22:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found