Perl Best Practices

16.8. Attribute Demolition

Use Class::Std to automate the deallocation of attribute data.

As mentioned under "Destructors" in Chapter 15, one of the very few annoyances of using inside-out objects rather than blessed hashes is the inevitable need to write separate clean-up code for every attribute, as in Example 16-11.

Example 16-11. Cleaning up object attributes

package Book; use Class::Std; {

# Attributes... my %title_of; my %author_of; my %publisher_of; my %year_of; my %topic_of; my %style_of; my %price_of; my %rating_of;

# and then... sub DEMOLISH { my ($self, $ident) = @_;

# Update library information... Library->remove($self);

# Clean up attribute hashes... delete $title_of{$ident}; delete $author_of{$ident}; delete $publisher_of{$ident}; delete $year_of{$ident}; delete $topic_of{$ident}; delete $style_of{$ident}; delete $price_of{$ident}; delete $rating_of{$ident}; return; } }

This kind of highly repetitive code structure is inherently error-prone to set up, unbearably tedious to read, and unnecessarily hard to maintain. For example, are you confident that the DEMOLISH( ) method shown in Example 16-11 actually did clean up every one of the object's attributes?

The goal here is always exactly the same: to iterate through every attribute hash in the class and delete the $ident entry inside it. It would be much better if there were some way for the class itself to keep track of its attribute hashes, so the class itself could automatically step through those attributes and remove the appropriate element from each.

Of course, you could do that "manually", by creating an array of references to the class's attribute hashes and then iterating that array with a for loop. For example:

package Book; {

# Declare attribute hashes, and construct a list of references to them

# (the \(...) applies the \ operator to each element of the list)... my @attr_refs = \( my %title_of, my %author_of, my %publisher_of, my %year_of, my %topic_of, my %style_of, my %price_of, my %rating_of, my %sales_of, );

# Clean up attributes when object is destroyed... sub DEMOLISH { my ($self, $ident) = @_;

# Update library information... Library->remove($self);

# Clean up attribute hashes... for my $attr_ref (@attr_refs) { delete $attr_ref->{$ident}; } return; } }

But then you'd need to write essentially the same DEMOLISH( ) in every class. The code for declaring and collecting the attributes is pretty scary, too.

The Class::Std module provides a simpler way to accomplish precisely the same goal. It provides a "marker" (:ATTR) that can be appended to the declaration of each attribute hash[*]. Whenever that marker is used, Class::Std stores a reference to the marked hash and then automatically applies the appropriate delete call after the class's DEMOLISH( ) method has been called. So the previous code could be rewrittenwith exactly the same functionalitylike so:

[*] Confusingly, these kinds of markers are also known as "attributes" in Perl (see the standard perlsub documentation), even though they have nothing to do with the data members of an object.

package Book; use Class::Std; { my %title_of :ATTR; my %author_of :ATTR; my %publisher_of :ATTR; my %year_of :ATTR; my %topic_of :ATTR; my %style_of :ATTR; my %price_of :ATTR; my %rating_of :ATTR; my %sales_of :ATTR;

# and then... sub DEMOLISH { my ($self) = @_;

# Update library information... Library->remove($self); return; } }

With this version, the necessary attribute-hash deletions would be performed automatically, immediately after the universal destructor's call to DEMOLISH( ) was finished. And if the class didn't define a DEMOLISH( ) method at all, the destructor would still perform the deletions at the appropriate time.

Note too that the # Attributes... comment has been omitted from this second version; the column of :ATTR markers is sufficient documentation.

As a final improvement to maintainability, a single :ATTR marker (or its synonym, :ATTRS) can also be applied to an entire list of attribute-hash definitions. So the previous code could be further reduced to:

package Book; use Class::Std; { my ( %title_of, %author_of, %publisher_of, %year_of, %topic_of, %style_of, %price_of, %rating_of , %sales_of, ) :ATTRS;

# and then... sub DEMOLISH { my ($self) = @_;

# Update library information... Library->remove($self); return; } }

Категории