PHP Patterns: Introduction (continued)
[ continued from part 1 of PHP Patterns: Introduction ]
Favor Composition over Inheritance
This principle was something of a revelation to me when I first started working with patterns. Coding to an interface is second nature to most object-oriented programmers because of the central role of inheritance, and therefore of polymorphism. In some ways, this is the problem. Inheritance is such a neat way of handling differences in implementation that it is tempting to use it for every variation you wish to introduce.
It is often better to combine objects at runtime in order to manage different conditions than to attempt to handle every case within the structure of your class hierarchy. You cannot always anticipate the cases you will need to handle, making your code inflexible, and worse, when variations are fine-grained you can find yourself duplicating functionality across child classes. The example below well illustrates this principle.
As we shall see, favoring composition does not mean eradicating inheritance. In fact, inheritance can be well used as a way of promoting composition.
When you find yourself subclassing in order to handle many minor differences, the next principle is worth bearing in mind.
Encapsulate the Concept that Varies
If we are to favor composition over inheritance, how do we define the components that should be combined at runtime? The Gang of Four suggest that you look at those elements of your code that are subject to variation. Such variation may be manifested in an inheritance tree, especially where there are many concrete implementations designed to address specific conditions.
Take a look at figure 2. This shows a class hierarchy which has become confused about its core responsibilities.
The Vehicle class is subclassed according to two separate forces,
Xml and Serial, which determine perhaps how Vehicle
data is saved, and Land and Sea. These are both varying
concepts, but it is clear which is most relevant to the Vehicle type.
Subclassing for both sets of differences leads us to inevitable duplication.
In Figure 3 we encapsulate the concept that varies, and, incidentally deploy composition along the way.
When applying this principle, the variation we are interested in will tend to cut across the the main responsibility of the type or component. Your component may be responsible for presenting page data, for example. A variation like a particular database platform is a distraction from this purpose ƒ_" which is where the DB package comes in so useful. If the variation runs with the grain of a class's responsibility, on the other hand, then subclassing for the difference may be the right strategy.
Pattern Principles at Work: A Brief Example
This section shows a pattern deployed and considers how it illustrates the principles above.
Imagine that you are asked to create a database cursor. A component that encapsulates a
result handle, returning each row upon request. Part of the brief is to provide filtering
of the data for different contexts. You may need to encode HTML entities, and add
<BR/> or <P /> tags for presentation on a Web page.
Alternatively you may need to decode entities, and remove tags. You are told that new
requirements for filtering data will arise.
First of all we separate the class responsible for iterating through the result set from
the classes that will filter each row. Here is the DBCursor class:
class DBCursor
implements Iterator {
|
Although this class may look complicated, in fact it does little more than to implement
the Iterator interface so that a DBCursor object can be fed to a
foreach loop. A neat trick, but not of interest here. What we really care about is
the process() method. This is called by any method that returns a database row.
The row in question is first passed to process() and then on to a RowFilter
object for transformation. So we're already encapsulating the varying element here. The
details of transformation are hidden behind the RowFilter interface.
Here is the RowFilter superclass and two concrete children:
abstract class RowFilter
{
|
The abstract superclass defines a concrete transform() method which loops through
the elements in the provided row, calling the abstract doTransform() method on
each name value pair. The two concrete classes apply crude transformations to the values
they are passed. EncodeRowFilter encodes HTML entities and converts newline
characters to <BR /> tags. DecodeRowFilter strips out all tags and
decodes all entities. Both the filter classes strip leading and trailing white space
using the trim() function.
Figure 4 shows the class diagram for this structure. You should find it familiar from the DB example above.
It is only when we add a few new conditions that cracks begin to appear in this design.
Each of the concrete classes performs multiple transforms. What happens, though, when we
need to handle many more kinds of transformations? We might need to switch specially
delineated keywords with look up values, for example, combining this with the
functionality already supported. Although sometimes we might need the dictionary
transformation to take place on its own. Sometimes, again, we could need line break
translation to be dropped in favor of wrapping <P /> tags, while supporting
dictionary transformation but not entity transformation.
We will soon drown in a sea of conditional statements or choke in a forest of subclasses. Either way, the results won't be pretty. Figure 5 illustrates the pleasures in store.
The problem here doesn't lie with the number of classes ƒ_" it is rather the fact that strategies are necessarily duplicated from class to class. In our simple example this probably wouldn't matter too much, but should our algorithms become more complex, then we face a real maintenance problem. Improving a particular implementation would necessitate similar changes wherever the algorithm occurs.
Remember the principles 'favor composition over inheritance' and 'encapsulate the concept that varies'? Clearly building all our transformation sets into subclasses is not going to work. Each transformation is a varying concept and each needs its own class. We then need to find a way to combine the newly independent algorithms to work together.
If you flick through the Gang of Four book you might come across the Decorator pattern which has the following Intent section:
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.It's uncanny! Almost as if I planned it that way!
The Decorator pattern allows you to combine objects of the same type into pipelines at runtime. This can make for highly configurable systems. What's more, you can add new Decorator classes when you need to with no need to amend any context.
I will examine this example in more detail later in this series, but for now, Figure 6 shows how we might deploy the Decorator pattern:
The abstract RowFilter class is unchanged. It provides the concrete
transform() method that loops through the provided array and calls
doTransform() upon each key/value pair. It is for child classes to provide
implementations of doTransform(). The simplest example of this is
PlainRowFilter which trims whitespace from the beginning and the end
of values, this transformation is always required. Here are RowFilter
and BasicRowFilter:
abstract class RowFilter
{
|
We can now list rows in the database like this, stripping white space as we go:
require_once("DB.php");
|
At this stage our only choice is to instantiate a BasicRowFilter object. In order
to combine other filters we need the DecoratorRowFilter class. This defines a
crucial piece of functionality. Its constructor accepts another RowFilter
object which is stored in a protected property (remember, protected means that a
property can be accessed by child classes while remaining inaccessible from the outside).
Concrete decorator classes now need only define a single method: doTransform().
They do have an additional responsibility, however. Before performing their operation
they must call doTransform() upon their stored RowFilter object.
It is this call that guarantees the magic of the Decorator pattern.
Here is the DecoratorRowFilter class and some concrete children:
abstract class DecoratorRowFilter extends RowFilter {
|
The Decorator pattern is typical in that it defines simple components that can be
combined into powerful dynamic structures. Using this class hierarchy we can build
pipelines of filters, combining individual components in any order. We create the
structure in one go when we instantiate the RowFilter object:
$db = DB::Connect("sqlite://./venue.db");
|
Notice the way that the constructor calls are nested. This is a telltale sign of the
Decorator pattern. If you are familiar with Java you will have seen similar calls in code
that works with the the java.io package. To read the nesting, you should work from
the inside out. We want the basic transformation to be applied, then the entity encoding
and then the line breaks added. If we're asked to apply dictionary transformations after
the formatting, then it's simply a matter of writing a new DecoratorRowFilter
object and adding it to the instantiation:
$filter = new
LineBreakRowFilter(new EncodeEntityRowFilter(new DictionaryRowFilter(new BasicRowFilter())));
|
The client class (DBCursor in this case) neither knows nor cares about the
number or configuration of objects in this structure. It has a reference to a single
RowFilter object with a guaranteed interface. What that RowFilter
object does with its siblings behind closed doors to achieve the transformation is its own
business. At the risk of laboring the point, DBCursor is interested only in the
RowFilter interface. We follow the principle: code to an interface, not to an
implementation.
Faced with requirements that were subject to frequent change, we found that building
functionality into a set of classes was insufficiently flexible. Our initial intent,
therefore, was to find a way to add new features to an object at runtime. The Decorator
pattern uses inheritance to promote composition. In other words, the fact that individual
RowFilter objects share a common type means that we can safely combine them using
type hinting. Every component added to the structure is guaranteed to support
transform() and doTransform() methods. Functionality can now be
determined at runtime by composing object structures, winning us increased flexibility and
ease of extension. This pattern therefore illustrates the principle: favor composition
over inheritance.
In order to determine the individual DecoratorRowFilter classes we examined
the forces that were causing us headaches. These were clearly the individual transformation
strategies. Extracting these was a matter of applying the principle: encapsulate the
concept that varies.
Summary
Patterns are at once simple and complex. They are, after all, no more than a set of instructions for recognizing problems and applying solutions. A pattern can be described using a handful of components, a diagram or two, and a code example. Yet patterns often require careful thought before they give up their secrets. Some have a revelatory aspect, a neat twist that can strike you with genuine pleasure at that moment it all comes together, the moment that you suddenly get it and you see how those few participants might actually change the way you code.
This article provided an introduction to design patterns with PHP. In the forthcoming companion pieces I will look at some classic design patterns in detail, focusing, of course, on PHP and upon the new features in PHP 5 that are particularly pattern-friendly.
Bibliography
Core J2EE Patterns
Deepak Alur, John Crupi, Dan Malks, Core J2EE Patterns: Best Practices and Design
Strategies (Prentice Hall PTR, 2001).
Design Patterns
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns. Elements of
Reusable Object-Oriented Software (Addison Wesley, 1995).
A Pattern Language
Christopher Alexander, Sara Ishikawa, Murray Silverstein, Max Jacobson, Ingrid
Fiksdahl-King and Shlomo Angel. A Pattern Language (Oxford University Press 1977)
About the Author
Matt Zandstra is a developer, teacher and writer specializing in object-oriented Internet applications. He currently works as an engineer at Yahoo! where he helps develop a core template management system. He is the author of SAMS Teach Yourself PHP in 24 Hours, and the recently released PHP 5: Objects, Patterns and Practice (http://www.amazon.com/exec/obidos/tg/detail/-/1590593804). He has written articles on PHP for Linux Magazine, IBM, Zend.com and at bgz-consultants.com.

Comments