Perl Design Patterns, Part 2
by Phil Crow
|
Pages: 1, 2, 3, 4
Template Method
In some calculations the steps are known, but what the steps do is not. For example, computing charges for a rental might involve three steps:
- Calculate amount due from rates.
- Calculate taxes.
- Add these together.
Yet different rentals might have different schemes for calculating the amount due from rates, and different jurisdictions usually have different tax schemes. A template method can implement the outline, deferring to callers for the individual schemes.
package Calc;
use strict; use warnings;
sub calculate {
my $class = shift; # discarded
my $data = shift;
my $rate_calc = shift; # a code ref
my $tax_calc = shift; # also a code ref
my $rate = &$rate_calc($data);
my $taxes = &$tax_calc($data, $rate);
my $answer = $rate + $taxes;
}
Here the caller supplies a data reference (probably to a hash or object)
together with two code references which are used as callbacks. Each
callback must expect the data reference as its first parameter. The tax_calc
code reference also receives the amount due from the rate calculator.
This allows it to use a percentage of the amount together with information in
the data reference.
A caller might look like this:
#!/usr/bin/perl
use strict; use warnings;
use Calc;
my $rental = {
days_used => 5,
day_rate => 19.95,
tax_rate => .13,
};
my $amount_owed = Calc->calculate($rental, \&rate_calc, \&taxes);
print "You owe $amount_owed\n";
sub rate_calc {
my $data = shift;
return $data->{days_used} * $data->{day_rate};
}
sub taxes {
my $data = shift; # discarded
my $subtotal = shift;
return $data->{tax_rate} * $subtotal;
}
I made this contrived caller so you can see the calling sequence. The
data here is a simple hash. To save exporting from Calc, I made calculate
a class method, so I call it through its class. In the call, I pass a
reference to my data hash and references to the two calculation routines.
This can be made more complex if you like. One could even make a full-blown class hierarchy of calculators, allowing callers to select the one they want. This example is about as simple as I could make the template method pattern.
Another approach to templates is to have the caller place methods in the template package. This approach amounts to an implementation of mixins a la Ruby. Here's a sample that is more object oriented.
package Calc;
sub calculate {
my $self = shift;
my $rate = $self->calculate_rate();
my $tax = $self->calculate_tax($rate);
return $rate + $tax;
}
1;
The whole module is really only the template method. To use it, you
have to code calculate_rate and calculate_tax methods, or your script
will die. Here's a particular implementation of the scheme:
package CalcDaily;
package Calc;
use strict; use warnings;
sub new {
my $class = shift;
my $self = {
days_used => shift,
day_rate => shift,
tax_rate => shift,
};
return bless $self, $class;
}
sub calculate_rate {
my $data = shift;
return $data->{days_used} * $data->{day_rate};
}
sub calculate_tax {
my $data = shift; # discarded
my $subtotal = shift;
return $data->{tax_rate} * $subtotal;
}
1;
Note that I added a constructor and two methods to the Calc package
in a different source file. This is perfectly legal and occasionally useful.
By doing this, the template is totally isolated. It doesn't even know
what sort of data will be stored in the objects of its own type. That
does mean that only one Calc subtype can be used at a time. If that's
a problem for you, do the standard thing: have Calc call methods on objects
in some separate hierarchy.
There are two package statements at the top of the file, this is on purpose.
The first one tells people (and crawlers) that this is the CalcDaily
package which rightfully belongs in CalcDaily.pm, not the original Calc, which belongs in Calc.pm.
Finally, here's the caller, which is only slightly modified:
#!/usr/bin/perl
use strict; use warnings;
use Calc;
use CalcDaily;
my $rental = Calc->new(5, 19.95, .13);
my $amount_owed = $rental->calculate();
print "You owe $amount_owed\n";
This technique is similar to the one used in the debugger architecture
for Perl. To make my own debugger, I need a name for it. I might choose
PhilDebug.pm. Then I have to make a file with that name in a Devel
directory which is in my @INC list. The first line in the
file should be (but doesn't have to be):
package Devel::PhilDebug;
This allows the CPAN indexer to properly catalog my module.
The base package for debuggers is fixed as DB. Perl expects to call
the DB function in that package. So all together it might look something
like this:
package Devel::PhilDebug;
package DB;
sub DB {
my @info = caller(0);
print "@info\n";
}
1;
Any script will use this debugger if it is invoked as:
perl -d:PhilDebug script
Each time the debugger notices that a new statement is about to start, it
first calls DB::DB. This is a very powerful example of plug-and-play.
It is not usually wise to pollute foreign classes with your own code. Yet, Perl permits this, because it is sometimes highly useful. There seems to be a theme here:
Don't rule out dangerous things. Just avoid them, unless you have a good reason to use them.
The Strategy and Template patterns use code references to allow the caller to adjust the behavior of an algorithm. The template I showed used a data container to hold rental information. The next pattern makes more use of data containers.
Builder
Many structures external to your program should be represented with composites (like trees or the data container in the introduction) inside your program. There are two fundamentally different ways to represent these structures. For an object-oriented way to compose such structures see the Composite Pattern in GoF (which I will discuss in my next article).
Here we'll look at how to build a composite structure in a hash of hashes. You might rather build the objected-oriented version. Which you choose should depend on the complexity of the data and the methods to act on it. If data and methods are simple, you should probably use the hash structure. It will be faster, have built-in support, and be more familiar to Perl programmers who might need to maintain your code. If the complexities are large, you should use full-blown objects. They make the structure easier to understand for object-oriented programmers and provide more code-based documentation than simple hashes.

