The Phrasebook Design Pattern
And Class::Phrasebook
by Rani PinchukOctober 22, 2002
Have you ever written a Perl application that connects to a database? If so, then you probably faced a problem of having some code like:
$statement = q(select isbn from book where title = 'Design Patterns');
$sth = $dbh->prepare($statement) ;
$rc=$sth->execute();
Why is this "a problem"? It looks like nice code, doesn't it? Actually, if you look at the code above, you will see two languages: Perl and SQL. This makes the code not that readable. Besides, SQL statements that are only slightly different may appear in several places, and this will make it more difficult to maintain. In addition, suppose you have an SQL expert who should optimize your SQL calls. How do you let him work on the SQL; should he look through your Perl code for it? And what if that guy is so ignorant that he doesn't even know Perl?
We can solve these problems by writing:
$statement = $sql->get("FIND_ISBN", { title => "Design Patterns" });
$sth = $dbh->prepare($statement);
$rc=$sth->execute();
We keep the actual SQL statements and their corresponding lookup keys in a separate place that we call a phrasebook. We can use XML to wrap the SQL and the lookup keys:
...
<phrase name="FIND_ISBN">
select isbn from book where title = '$title'
</phrase>
...
As we can see above, the SQL statement can hold placeholders, so we can use one generic SQL statement that can be used in different places.
Now, our code is more readable and maintainable. As we will see later, there are additional advantages for writing using the phrasebook design pattern - it helps when we port the code, or when we debug.
Nevertheless, is it a design pattern at all? We saw only one problem and one solution, right? So here are two more examples: When you need to generate an error code from your application, you might have in your code two languages: Perl and English. Moreover, suppose you want to have later the error code in other languages? The phrasebook design pattern suggests that you should separate the languages, and put the error messages in a phrasebook. It also guides you as to how to have the error messages in different languages.
Suppose you write a code generator that generates PostScript. In order to have readable code, it is better to separate the PostScript code from the rest of your code, and put it in a phrasebook.
If you are particularly interested, then you can find the original Phrasebook Design Pattern paper in http://jerry.cs.uiuc.edu/~plop/plop2k/proceedings/Pinchuk/Pinchuk.pdf.
Because our examples above were about writing applications that uses database, I would like to add a paragraph or two about persistency. Some readers might argue that instead of having the SQL code within our Perl code, or even within our phrasebook, we should create objects that take the responsibility for connecting to the database, and load or save themselves. This way, if we use those objects, we need no SQL in the rest of our code. There are two points to make in this context:
I would suggest the use of the phrasebook within the classes that implement those persistence objects. This way, the code of those classes will gain from the advantages that the phrasebook pattern offers.
Usually a persistence object represents one or more tables in the database. The idea is that we should not access those tables without using the object. Yet, in my experience, because of performance issues, you may need complex SQL statements that access tables belonging to more then one object. So the programmer might find himself writing an SQL statement in his main application anyway, instead of using the persistence objects.
Class::Phrasebook
You probably guessed already that the phrasebook class implements the phrasebook design pattern. Like the rest of the classes that are described in this paper, it is available for downloading from CPAN. Let us see how we use that class to generate some error codes in different languages (English and Dutch for example).
We begin with writing the phrasebook. The phrasebook is a simple XML file. It will look like:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE phrasebook [
<!ELEMENT phrasebook (dictionary)*>
<!ELEMENT dictionary (phrase)*>
<!ATTLIST dictionary name CDATA #REQUIRED>
<!ELEMENT phrase (#PCDATA)>
<!ATTLIST phrase name CDATA #REQUIRED>
]>
<phrasebook>
<dictionary name="EN">
<phrase name="MISUSE_OF_MANUAL_TEMPLATE_NAME">
The name $name can be used only as manual template
</phrase>
...
</dictionary>
<dictionary name="NL">
<phrase name="MISUSE_OF_MANUAL_TEMPLATE_NAME">
De naam $name mag enkle gebriuk worden als webboek template
</phrase>
...
</dictionary>
</phrasebook>
As we can see, the phrasebook file starts with the Document Type Definition (DTD). Don't panic - just copy it as is. It is used by the XML parser to validate the XML code. Then we open the definition of the phrasebook, and inside it one or more definitions of dictionaries. Each dictionary will hold the phrases. The first dictionary is taken as the default dictionary: if a phrase is missing in other dictionary, it will be taken from the first one. The phrases can hold placeholders. The placeholders look exactly like Perl scalar variables.
Now let's see how we get a phrase from the phrasebook:
...
$msg = new Class::Phrasebook($log, "errors.xml");
$msg->load($language);
...
# check that the name of the document is not a manual_template name
if (is_manual_template_name($template_name)) {
my $message = $msg->get("MISUSE_OF_MANUAL_TEMPLATE_NAME",
{ name => "$template_name"} );
$log->write($message, 5);
return 0;
}
First, we create the Class::Phrasebook object. We supply to the constructor a Log::LogLite object and a name of the XML file that contains the phrasebook we want to use. We will discuss the Log::LogLite class later. For now, you should only know that this class provides with the ability to have log messages - so if, for example, our new Class::Phrasebook object fails to find the XML file, a log message will be written to a log file.
How the XML file will be found if we do not supply any path? The class automatically searches the following directories:
- The current directory.
- The directory ./lib in the current directory.
- The directory ../lib in the current directory.
- @INC.
This allows us to create an XML file that comes with a module in an easy way: We should place the XML file in a lib directory below the directory of our module. Therefore, if the module we write is Blah, then its XML file will be placed in the directory Blah/lib. Of course, it is a good idea to give the XML file a name that is similar to the module name - blah.xml, so it will be unique on the system (because "make install" will install the XML file next to the module file Blah.pm).
Pages: 1, 2 |





