
Perl Design Patterns
by Phil CrowJune 13, 2003
Introduction
Though these articles are self-contained, you will get more out of them if you are familiar with the GoF book (or better yet have it open on your desk while you read). If you don't have the book, then try searching the Web - many people talk about these patterns. Since the Web and the book have diagrams of the object versions of the patterns, I will not reproduce those here, but can direct you to this fine site.
I will show you how to implement the highest value patterns in Perl, most often by using Perl's rich core language. I even include some objects.
For the object-oriented implementations, I need you to understand the basics of Perl objects. You can learn that from printed sources like the Perl Cookbook by Tom Christiansen and Nat Torkington or Objected Oriented Perl by Damian Conway. But the simplest way to learn the basics is from perldoc perltoot.
As motivation for my approach, let me start with a little object-oriented philosophy. Here are my two principles of objects:
- Objects are good when data and methods are tightly bound.
- In most other cases, objects are overkill.
Let me elaborate briefly on these principles.
Objects are good when data and methods are tightly bound.
When you are working for a company that rents cars (as I do), an object to represent a rental agreement makes sense. The data on the agreement is tightly bound to the methods you need to perform. To calculate the amount owed, you take the various rates and add them together, etc. This is a good use of an object (or actually several aggregated objects).
In most other cases, objects are overkill.
Consider a few examples from other languages. Java has the java.lang.Math
class. It provides things such as sine and cosine. It only provides class
methods and a couple of class constants. This should not be forced into
an object-oriented framework, since there are no Math objects. Rather
the functions should be put in the core, left out completely, or made into
non-object-oriented functions. The last option is not even available
in Java.
Or think of the C++ standard template library. The whole templating framework is needed to make C++ backward compatible with C and to handle strong static-type checking. This makes for awkward object-oriented constructs for things that should be simple parts of the core language. To be specific, why shouldn't the language just have a better array type at the outset? Then a few well-named built-in operations take care of stacks, queues, dequeues and many other structures we learned in school.
So, in particular, I take exception to one consistent GoF trick: turning an idea into a full-blown class of objects. I prefer the Perl way of incorporating the most-important concepts into the core of the language. Since I prefer this Perl way, I won't be showing how to objectify things that could more easily be a simple hash with no methods or a simple function with no class. I will invert the GoF trick: implement full-blown pattern classes with simpler Perl concepts.
The patterns in this first article rely primarily on built-in features of Perl. Later articles will address other groups of patterns. Now that I've told you what I'm about to do, let's start.
Iterator
There are many structures that you need to walk one element at a time. These include simple things such as arrays, moderate things such as the keys of a hash, and complex things such as the nodes of a tree.
The Gang of Four suggest solving this problem with the above mentioned trick: turn a concept into an object. Here that means you should make an iterator object. Each class of objects that can reasonably be walked should have a method that returns an iterator object. The object itself always behaves in a uniform way. For example, consider the following code, which uses an iterator to walk the keys of a hash in Java.
for (Iterator iter = hash.keySet().iterator(); iter.hasNext();) {
Object key = iter.next();
Object value = hash.get(key);
System.out.println(key + "\t" + value);
}
The HashMap object has something that can be walked: its keys. You can ask it for this keySet. That Set will give you an Iterator on request to its iterator method. The Iterator responds to hasNext with a true value if there are more things to be walked, and false otherwise. Its next method delivers the next object in whatever sequence the Iterator is managing. With that key, the HashMap delivers the next value in response to get(key). This is neat and tidy in the completely OO framework of a language with limited operators and built-in types. It also perfectly exhibits the GoF iterator pattern.
In Perl any built-in or user defined object which can be walked has a method which returns an ordered list of the items to be walked. To walk the list, simply place it inside the parentheses of a foreach loop. So the Perl version of the above hash key walker is:
foreach my $key (keys %hash) {
print "$key\t$hash{$key}\n";
}
I could implement the pattern exactly as it is diagrammed in GoF,
but Perl provides a better way. In Perl 6, it will even be possible
to return a list that expands lazily, so the above will be more
efficient than it is now. In Perl 5, the keys list is built completely
when I call keys. In the future, the keys list will be built on
demand, saving memory in most cases, and time in cases where the loop
ends early.
The inclusion of iteration as a core concept represents Perl design at its finest. Instead of providing a clumsy mechanism in non-core code, as Java and C++ (through its standard template library) do, Perl incorporates this pattern into the core of the language. As I alluded to in the introduction, there is a Perl principle here:
If a pattern is really valuable, then it should be part of the core language.
The above example is from the core of the language. To see that foreach
fully implements the iterator pattern, even for user-defined modules,
consider an example from CPAN: XML::DOM. The DOM for XML was specified
by Java programmers. One of the methods you can call on a DOM Document is
getElementsByTagName. In the DOM specification this returns a
NodeList,
which is a Java Collection. Thus, the NodeList works like the Set in the
Java code above. You must ask it for an Iterator, then walk the Iterator.
When Perl people implemented the DOM, they decided that
getElementsByTagName
would return a proper Perl list. To walk the list one says something like:
foreach my $element ($doc->getElementsByTagName("tag")) {
# ... process the element ...
}
This stands in stark contrast to the overly verbose Java version:
NodeList elements = doc.getElementsByTagName("tag");
for (Iterator iter = elements.iterator(); iter.hasNext();) {
Element element = (Element)iter.next();
// ... process the element ...
}
One beauty of Perl is its ability to combine procedural, object-oriented, and core concepts in such powerful ways. The facts that GoF suggests implementing a pattern with objects and that object only languages like Java require it do not mean that Perl programmers should ignore the non-object features of Perl.
Perl succeeds largely by excellent use of the principle of promotion. Essential patterns are integrated into the core of the language. Useful things are implemented in modules. Useless things are usually missing.
So the iterator pattern from GoF is a core part of Perl we hardly think about. The next pattern might actually require us to do some work.
Decorator
In normal operation, a decorator wraps an object, responding to the same
API as the wrapped object. For example, suppose I add a compressing
decorator to a file writing object. The caller passes a file
writer to the decorator's constructor, and calls write on the decorator.
The decorator's write method first compresses the data, then calls the
write method of the file writer it wraps. Any other type of
writer could be wrapped with the same decorator, so long as all writers
respond to the same API. Other decorators can also be used in a chain.
The text could be converted from ASCII to unicode by one decorator and
compressed by another. The order of the decorators is important.
In Perl, I can do this with objects, but I can also use a couple of language features to obtain most of the decorations I need, sometimes relying solely on built-in syntax.
I/O is the most common use of decoration. Perl provides I/O decoration directly. Consider the above example: compressing while writing. Here are two ways to do this.
Use the Shell and Its Tools
When I open a file for writing in Perl, I can decorate via shell tools. Here is the above example in code:
open FILE, "| gzip > output.gz"
or die "Couldn't open gzip and/or output.gz: $!\n";
Now everything I write is passed through gzip on its way to output.gz. This works great so long as (1) you are willing to use the shell, which sometimes raises security issues; and (2) the shell has a tool to do what you need done. There is also an efficiency concern here. The operating system will spawn a new process for the gzip step. Process creation is about the slowest thing the OS can do without performing I/O.
Tying
If you need more control over what happens to your data, then you can decorate it yourself with Perl's tie mechanism. It will be even faster, easier to use, and more powerful in Perl 6, but it works in Perl 5. It does work within Perl's OO framework; see perltie for more information.
Suppose I want to preface each line of output on a handle with a time stamp. Here's a tied class to do it.
package AddStamp;
use strict; use warnings;
sub TIEHANDLE {
my $class = shift;
my $handle = shift;
return bless \$handle, $class;
}
sub PRINT {
my $handle = shift;
my $stamp = localtime();
print $handle "$stamp ", @_;
}
sub CLOSE {
my $self = shift;
close $self;
}
1;
This class is minimal, in real life you need more code to make the
decorator more robust and complete. For example, the above code does
not check to make sure the handle is writable nor does it provide
PRINTF,
so calls to printf will fail. Feel free to fill in the details.
(Again, see perldoc perltie for more information.)
Here's what these pieces do. The constructor for a tied file
handle class is called TIEHANDLE. Its name is fixed and uppercase,
because Perl calls this for you. This is a class method, so the
first argument is the class name. The other argument is an open
output handle. The constructor merely blesses a reference to this
handle and returns that reference.
The PRINT method receives the object constructed in TIEHANDLE plus
all the arguments supplied to print. It calculates the time stamp
and sends that together with the original arguments to the handle
using the real print function. This is typical decoration at work.
The decorating object responds to print just like a regular handle
would. It does a little work, then calls the same method on the
wrapped object.
The CLOSE method closes the handle. I could have inherited from
Tie::StdHandle to gain this method and many more like it.
Once I put AddTimeStamp.pm in my lib path, I can use it like this:
#!/usr/bin/perl
use strict; use warnings;
use AddStamp;
open LOG, ">output.tmp" or die "Couldn't write output.tmp: $!\n";
tie *STAMPED_LOG, "AddStamp", *LOG;
while (<>) {
print STAMPED_LOG;
}
close STAMPED_LOG;
After opening the file for writing as usual, I use the built-in tie
function to bind the LOG handle to the AddStamp class under the name
STAMPED_LOG. After that, I refer exclusively to STAMPED_LOG.
If there are other tied decorators, then I can pass the tied handle to them. The only downside is that Perl 5 ties are slower than normal operations. Yet, in my experience, disks and networks are my bottlenecks so in memory inefficiency like this tends not to matter. Even if I make the script code execute 90 percent faster, I don't save a noticeable amount of time, because it wasn't using much time in the first place.
This technique works for many of the built-in types: scalars, arrays, hashes, as well as file handles. perltie explains how to tie each of those.
Ties are great since they don't require the caller to understand the magic you are employing behind their back. That is also true of GoF decorators with one clear exception: In Perl, you can change the behavior of built-in types.
Pages: 1, 2 |

