Perl Best Practices

15.9. Methods

When creating methods, follow the general guidelines for subroutines.

Despite their obvious differences in dispatch semantics, methods and subroutines are similar in most respects. From a coding point of view, about the only significant difference between the two is that methods tend to have fewer parameters[*].

[*] If that proves not to be the case, you should probably re-evaluate your design. Do certain combinations of arguments regularly appear together? Perhaps they ought to be encapsulated in an object of their own that's then passed to the method. Or maybe they ought to be attributes of the invocant itself.

When you're writing methods, use the same approach to layout (Chapter 2), and the same naming conventions (Chapter 3), and the same argument-passing mechanisms and return behaviours (Chapter 9), and the same error-handling techniques (Chapter 13) as for subroutines.

The only exception to that advice concerns naming. Specifically, the "Homonyms" guideline in Chapter 9 doesn't apply to methods. Unlike subroutines, it's acceptable for a method to have the same name of a built-in function. That's because methods are always called with a distinctive syntax, so there's no possible ambiguity between:

$size = length $target;

# Stringify target object; take length of string

and:

$size = $target->length( );

# Call length( ) method on target object

It's important to be able to use builtin names for methods, because one of the commonest uses of object-oriented Perl is to create new data types, which often need to provide the same kinds of behaviours as Perl's built-in data types. If that's the case, then those behaviours ought to be named the same as well. For instance, the class in Example 15-5 is a kind of queue, so code that uses that class will be easier to write, and later comprehend, if the queue objects push and shift data using push( ) and shift( ) methods:

my $waiting_list = FuzzyQueue->new( );

# Load client names... while (my $client = prompt 'Client: ') { $waiting_list->push($client); }

# Then rotate the contents of the queue (approximately) one notch... $waiting_list->push( $waiting_list->shift( ) );

Naming those same methods append( ) and next( ) makes it slightly harder to work out what's going on (as you can't reason by analogy to Perl's builtins):

my $waiting_list = FuzzyQueue->new( ); # Load client names... while (my $client = prompt('Client: ')) { $waiting_list->append($client); } # Then rotate the contents of the queue (approximately) one notch... $waiting_list->append( $waiting_list->next( ) );

Example 15-5. A mildly stochastic queue

# Implement a queue that's slightly blurry about where it adds new elements... package FuzzyQueue; use Class::Std::Utils; use List::Util qw( max ); {

# Attributes... my %contents_of;

# The array storing each fuzzy queue's data my %vagueness_of;

# How fuzzy should the queue be?

# The usual inside-out constructor... sub new { my ($class, $arg_ref) = @_; my $new_object = bless anon_scalar( ), $class; $contents_of{ident $new_object} = []; $vagueness_of{ident $new_object} = exists $arg_ref=>{vagueness} ? $arg_ref=>{vagueness} : 1; return $new_object; }

# Push each element somewhere near the end of queue... sub push { my ($self) = shift;

# Unpack contents of queue... my $queue_ref = $contents_of{ident $self};

# Grab each datum... for my $datum (@_) {

# Scale the random fuzziness to the amount specified for this queue... my $fuzziness = rand $vagueness_of{ident $self};

# Squeeze the datum into the array, using a negative number

# to count (fuzzily) back from the end, but making sure not

# to run off the front... splice @{$queue_ref}, max(-@{$queue_ref}, -$fuzziness), 0, $datum; } return; }

Grab the object's data and shift off the first datum (in a non-fuzzy way)... sub shift { my ($self) = @_; return shift @{ $data_of{ident $self} }; } }

Категории