Patterns For PHP
Index Patterns News Forums

Strategy

From Patterns For PHP

Contents

The Strategy pattern defines an object that represents an algorithm for a particular task.

Introduction

In an object oriented application, whenever a new task is identified a programmer's response is usually to create a new class to represent it. Over time however the class may evolve to include numerous sub tasks, each of which adds further complexity to the original class. In some cases, such sub tasks may have many alternatives. An example is a Logger class. It's original task is to write a log message to a file. In time one might expand its capablities to write to numerous targets: File and Database. An additional requirement might allow the message to be formatted in various ways: a simple String, XML, serialised data, or perhaps HTML.

Our class now has one primary task, Logging, and two distinct sub tasks, Formatting and Writing. Each sub-task has many competing implementations. The Strategy Pattern suggests a means of implementating a class structure which allows programmers manage such flexibility easily to allow future growth, while avoiding some common pitfalls met in allowing and supporting such flexibility. Furthermore, the Strategy Pattern demonstrates how composition is often far more powerful than simple sub classing.

Implementing the Strategy Pattern

At its heart, our Logging class desperately wishes to be simple. It accepts a message and writes it somewhere for storage. No matter how we refactor and extend the Logger we should aim at maintaining a simple interface. While in PHP 4 this was largely a matter of programmer discipline, PHP 5 offers the ability to enforce an interface. It's as simple as creating an Interface (something similar to a class) to which all possible concrete Logger classes must adhere.

php
interface iWriter {
 
    public function write();
 
}

A lengthy explanation of Interfaces is not required. All classes which implement an Interface must supply a list of methods as defined by the Interface. Failing to do so will generate a fatal error. We named our interface iWriter rather than iLogger, since the latter is a more generic description and may possibly be reused for another class family.

With an interface decided upon, let's revisit the Logger example. Our original Logger class might look similar to:

php
class Logger {
 
    private $file = null;
 
    public function __construct($file) {
        $this->file = $file;
    }
 
    public function write($message) {
        file_put_contents($this->file, array(PHP_EOL, $message), FILE_APPEND);
    }
 
}

This class is ultra simple. It is instantiated with the location of the file to store log messages to. When Logger::write() is called, it opens the file and appends the $message parameter value to the end of the file (preceded by a new line character). The array of values is automatically concatenated (just as if we used join()) and the FILE_APPEND flag ensures we do not overwrite any existing data in the file.

It's use should be quite obvious.

php
$logger = new Logger('/tmp/mylog');
$logger->write('This is a log message!');

Unfortunately, the requirements for our Logger may prove more complex. As noted in our introduction, the Logger must store messages to either a file or a database. Whichever we use, the simplest solution is to create sub classes of the Logger class. We would call these classes Logger_File and Logger_DB. Their implementation is not important right now.

The difficulty arises when we add a second variable element. Our requirements grow to include various methods of formatting a message before it is stored. This creates a lot of confusion. Sub classing excels at splitting variability between two options in a class into two new and separate classes, i.e. just as we did in considering Logger_File and Logger_DB sub classes to split the storage option into separate classes. Unfortunately, once the number of variances grow beyond one, sub classing produces something ugly. Consider our current sub classes, Logger_File and Logger_DB. If we add various formatting methods, we would need to sub class both of these in turn to support each formatting option. If our formatting options include String, XML and HTML our list of classes would resemble:

  • Logger_DB_String
  • Logger_DB_XML
  • Logger_DB_HTML
  • Logger_File_String
  • Logger_File_XML
  • Logger_File_HTML

We now have six subclasses of Logger.

We can make a few educated observations about this structure before writing so much as a single line of code.

  1. The task of formatting is duplicated for each of the two output options, both DB and File.
  2. Adding a new format would result in needing to add two new sub classes (one for each output option).
  3. Adding a new output would result in adding three new classes (duplicating all formats for the new output).
  4. 1-3 above are undesired effects which will make this design very difficult to maintain and extend.

These observations demonstrate a severe weakness of relying solely on sub classing to solve our problems. Each additional option will exponentially increase the number of classes we must add to support it. Adding just a few options would result in DOZENS of new classes, each of which adds to the confusion and duplication of code.

The solution? Well, you read this article's title I hope ;). The Strategy Pattern solves this issue very neatly by using the power of composition to replace the continually increasing depths of an expanding class hierarchy. The Pattern is actually quite simple, and something no few programmers with some experience will readily have suggested the moment we defined the Logger problem. They may never have even heard of the Strategy Pattern and simply stumbled upon the solution themselves.

The Strategy Pattern works by identifying each of those variable sub tasks and isolating them into a new class family. Let's consider the Logger sub class list again. The most variable task is the formatting. There are two output types and its unlikely we need many more. Formatting however is far more open. There could be dozens of ways the person using the Logger might want to format the message. We could in fact add a sprintf format to enable such flexibility without forcing programmers to define loads of custom formats. But this is outside the scope of our simple example.

Since formatting is the most variable and the most likely to see new options, we will cut it out of Logger completely. Formatting could be added to a new Formatter class family. We could create a superclass called Formatter and then subclass it for each formatting method. In our case, each formatting option has no common tasks - in the world of formatting each does its own unique thing. This means we can do away with a common parent class. Since a guiding principle of Design Patterns (and OOP in general) is to "code to an interface, not an implementation" we do however make certain each Formatter follows the same interface. In PHP 5, we can enforce this by defining an Interface.

php
interface iFormatter {
 
    public function format();
 
}

With an Interface in place, we can continue creating Formatter classes.

php
class Formatter_String implements iFormatter {
 
    public function format($message) {
        return $message.PHP_EOL;
    }
 
}
 
class Formatter_XML implements iFormatter {
 
    public function format($message) {
        $timestamp = time();
        // heredoc string syntax
        $xml = <<<XML_EOL
            <message>
                <time>$timestamp</time>
                <text>$message</text>
            </message>
XML_EOL;
        return $xml.PHP_EOL;
    }
 
}
 
class Formatter_HTML implements iFormatter {
 
    public function format($message) {
        $timestamp = time();
        // heredoc string syntax
        $html = <<<HTML_EOL
            <p>
            <b>Timestamp:</b> $timestamp
            <br />
            <b>Message:</b> $message
            </p>
HTML_EOL;
        return $html.PHP_EOL;
    }
 
}

With our Formatter class structure in place, we now consider how to utilise it within our Logger class. Without the formatting options, our Logger class family has shrunk to a far more manageable count of three classes. We have the Logger parent class, and two sub classes, Logger_File and Logger_DB.

The Logger parent class now looks like:

php
abstract class Logger implements iWriter {
 
    protected $formatter;
 
    protected function __construct($formatter) {
        if($formatter instanceof iFormatter)
        {
            $this->formatter = $formatter;
        }
        else
        {
            trigger_error('Invalid Formatter!', E_USER_ERROR);
        }
    }
 
}
 
class Logger_File extends Logger {
 
    private $file = null;
 
    public function __construct($formatter, $file) {
        parent::__construct($formatter);
        $this->file = $file;
    }
 
    public function write($message) {
        $formatted_message = $this->formatter->format($message);
        file_put_contents($this->file, $formatted_message, FILE_APPEND);
    }
 
}
 
class Logger_DB extends Logger {
 
    // $db in this case is a database connection object from ADOdb Lite
    private $db = null;
 
    public function __construct($formatter, $db) {
        parent::__construct($formatter);
        $this->db = $db;
    }
 
    public function write($message) {
        $formatted_message = $this->formatter->format($message);
        $_date = time();
        $_escaped_message = $db->qstr($message);
        $this->db->Execute('INSERT INTO app_log_messages (time, message) VALUES (' . $_date . ',' . $_escaped_message . ')');
    }
 
}

Whenever we create a new Logger class, we can now pass a Formatter object as a parameter via its constructor. This Formatter object, regardless of its actual type, will always follow the exact same interface. For this reason the Logger can use any Formatter very easily - use its write() method as enforced by the iWriter interface. If you consider the new structure - the Logger and the Formatter class family it uses via composition - there is a marked improvement over the original design which relied solely on sub classing.

Using our new Logger and the Formatter class type we created in applying the Strategy Pattern is as simple as:

php
// log a message in XML format to an XML file
 
$logger = new Logger_File(new Formatter_XML(), '/tmp/mylog.xml');
$logger->write('I am an XML formatted log message!');

The improvement in design is due to implementing the Strategy Pattern on Logger; divorcing the formatting variability into its own class type and associating the Logger with a Formatter by passing it into Logger as a construction parameter. During the 1990's, the Gang of Four in their "Design Patterns" book offered the following principle. Favour composition over subclassing. The Strategy Pattern demonstrates this principle in action very nicely.

What else can we say about the Strategy Pattern? Our Logging design is now far simpler and cleaner than it would have been using sub classing alone. Any potential code duplication has been prevented. Adding a new Formatter or a new Logging output option only requires ONE extra class. We have created a new class type independent of Logger, the Formatter, which improves potential reuse. The benefits ensure that the Strategy Pattern is one a programmer will certainly not want to forget.

Conclusion

Our simple implementation of the Strategy Pattern in the Logger example demonstrated both its simplicity and usefulness. It is a pattern which uses composition in favour of sub classing to reduce the complexity that may result from a growing class hierarchy where two or more class sub tasks are variable.

An important lesson to take from the Strategy Pattern implementation is that composition is something every programmer should be aware of. Sub classing is an immensely useful tool but in some complex scenarios it is simply not a good choice.

Retrieved from "/wiki/Strategy"
MediaWiki
GNU Free Documentation License 1.2