Perl Design Patterns, Part 3
by Phil CrowAugust 15, 2003
split or with the king: Parse::RecDescent, which
brings the best of yacc into your Perl script (albeit with somewhat
less efficiency than yacc).
This article continues my treatment by considering patterns which rely on objects. As such, this article's patterns bears the most resemblance to the GoF book. Before presenting some patterns, I'll give you my two cents about object applicability.
When Are Objects Good?
As Larry Wall reminds us about all programming constructs, you should use objects when they make sense and not when they don't. So when do they make sense? This is partly a matter of taste. This subsection gives you my tastes.
It's easier to say when objects are bad, which they are in these cases:
More on Perl Design Patterns:
• Perl Design Patterns, Part 1 |
- There is only data, the methods are either trivial or non-existent. Data containers (also called nodes) are like this. For example, I should not need an object to return three numbers and a string to my caller.
- There are only methods. The Java Math class is like this. It won't even let you make a Math object. Clearly its methods should just be built-in functions of the language.
Seeing the poor uses of objects gives insight into their effective use. Use objects when complexity is high and data is tightly coupled to the methods which act on it. High complexity makes these chief advantages of objects more important: separate namespaces, inheritance, and polymorphism.
Now that I've spoken my peace, I'll go on to the patterns which use objects.
Abstract Factory
If you want to build platform independent programs, you need a way to access the underlying systems without having to recode for each one's API. This is where a factory comes into play. The source code asks for an instance of a class, the class delivers a subclass instance suitable for use on the current platform. That class is called an abstract factory (or simply a factory). As we will see below, the platform might be a database. So the factory would return an object suitable for use with a particular database, but all the objects would have the same API.
To show the basic idea, here is an example which delivers one of two types. There are four code files in this example. The first two are the greeters.
package Greet::Repeat;
sub new {
my $class = shift;
my $self = {
greeting => shift,
repeat => shift,
};
return bless $self, $class;
}
sub greet {
my $self = shift;
print ($self->{greeting} x $self->{repeat});
}
1;
This greeter's constructor expects a greeting and a repeat count. It stores these in a hash, returning a blessed reference to it. When asked to greet, it prints the greeting repeatedly (hence the name). (I didn't say this example was practical, but it is small.)
package Greet::Stamp;
use strict; use warnings;
sub new {
my $class = shift;
my $greeting = shift;
return bless \$greeting, $class;
}
sub greet {
my $greeting = shift;
my $stamp = localtime();
print "$stamp $$greeting";
}
1;
This greeter only expects a greeting string, so it blesses a reference to the one it receives. When asked to greet, it prints the current time followed by the greeting.
Here's the factory:
package GreetFactory;
use strict; use warnings;
sub instantiate {
my $class = shift;
my $requested_type = shift;
my $location = "Greet/$requested_type.pm";
my $class = "Greet::$requested_type";
require $location;
return $class->new(@_);
}
1;
A Perl factory looks a lot like factories in other languages. This one has only one method. It returns the requested type to the caller. It uses the caller's requested type as the name of the class to instantiate and as the name of the Perl module in which the class lives.
Finally, you can use this factory with a script like this:
#!/usr/bin/perl
use strict; use warnings;
use GreetFactory;
my $greeter_n = GreetFactory->instantiate("Repeat", "Hello\n", 3);
$greeter_n->greet();
my $greeter_stamp = GreetFactory->instantiate("Stamp", "Good-bye\n");
$greeter_stamp->greet();
To make each greeter, call the instantiate method of GreetFactory, passing
it the name of the class you want and any arguments that class's constructor
is expecting.
This example shows you the basic idea. It is simple on purpose. But it
does show how the factory can be ignorant of the underlying classes.
Any new greeter added to the system must have a name of the form Greet::Name
and be placed into a Greet subdirectory of an @INC path member as
Name.pm. Then callers can use it without changing the factory. Now that
you have seen a simple example, here is a more useful one.
The Perl DBI (DataBase Interface) provides an excellent example of a factory.
Each call to DBI-connect>, expects the type of database and whatever
information that database needs to establish a connection. This is a classic
factory. It will load any DBD (DataBase Driver) you have installed
on your system, upon request. Additional DBD's can be added at any time.
Once they are installed, any client can use them through the same
DBI API. Here's an example use of DBI:
use DBI;
my $dbh =
DBI->connect("dbi:mysql:mydb:localhost", "user", "password");
...
my $sth = $dbh->prepare('select * from table');
...
Once the database handle is obtained (which is usually called $dbh), it
can be used almost without regard to the underlying engine. If you
later move to Oracle, you would merely change the connect call.
If a new database comes on the scene, some smart person in contact with
Tim Bunce will implement a class for it. You can install and switch
to it as soon as they finish their work. You might even be the implementer,
but I doubt I will be.
Composite
This pattern shows how to use the fully OO composite pattern. If you are interested in a simpler non-OO implementation see the Builder Pattern in Part 2 of this article series.
Many applications require hierarchies of related items linked into a tree by relationship. Many people see a hierarchy of this type: a directory structure. At the top is the root directory. In the simplest case it includes two types of items: files and subdirectories. Each subdirectory is like the root directory. Note that this definition of the structure is recursive, which is typical of composites.
One of the most popular examples of a composite structure today is an XML file. These files have a root element which contains various types of subelements, including tags and comments. Tags have attributes and some can contain subelements. This makes the classic composite tree. There are two important steps for a composite structure. The first is building it. The second is using it. We'll see simple examples of both here.
For the genuine pattern, there must be methods that act on both regular and composite elements (the elements with children are called composite elements). Invoking such a method on the root of a composite tree, or subtree, causes that root to do work on its own data AND to forward the request to its children. Those children do the same, collecting their own data and that of their children, until the bottom of the tree is reached. The return value is a collection of all this data.
For a practical example consider using the DOM model to process XML.
(You may obtain the XML::DOM module from CPAN.) To find all the paragraphs
in a document we could do something like this:
use XML::DOM;
my $parser = XML::DOM::Parser->new();
my $doc = $parser->parsefile("file.xml");
foreach my $paragraph ($doc->getElementsByTagName("paragraph")) {
print "<p>";
foreach my $child ($paragraph->getChildNodes) {
print $child->getNodeValue if ($child->getNodeType eq TEXT_NODE);
}
}
$doc->dispose();
The call to getElementsByTagName begins at the root (since I called it
through $doc). The root returns any of its children which are paragraphs,
but it also forwards the request to all of its tag elements asking them
to return their paragraphs. They do the same.
An unrelated note:
Notice that the above example ends with a call to dispose. XML::DOM
composite structures have references from parents to children and from
children to parents. We usually call these circular links. Perl 5
garbage collection cannot harvest such structures. We must call dispose
to break the extra links so the structure's memory can be recovered. If
you build structures with circular links, you must break those links
yourself, otherwise your program will leak memory.

