The Strategy Pattern: Design Patterns
Strategy is a design pattern that allows to change algorithm’s behaviour at runtime. This feature generally gives this pattern more flexibility. It can be usually recognized from algorithm classes implementing a strategy interface and a context class, which dynamically accepts algorithm objects via a strategy interface. E.g. we have our Database class, which may use a MySQLServer or PostgreSQLServer algorithm object to work with different database servers.
Examples usually make it easier to understand a concept, so let’s try and make one. Suppose we want to have a logging class that would be able to log to the database or to StdOut and we should be able to change the desired algorithm as we want (thus the choice cannot be hardcoded).
If we follow the UML diagram, first let’s create an algorithm interface:
<?php
interface iLogger {
public function log($message);
public function error($message);
public function info($message);
}
Supposedly our log algorithm classes will support log
, error
and info
log types (hence the same operations). Secondly, from the UML diagram we see that algorithm classes should implement our iLogger
interface. Let’s create separate classes for logging to the database and logging to stdOut:
<?php
class DatabaseLogger implements iLogger {
public function __construct($server) {
printf("Opening connection to the database server %s...\n", $server);
$this->server = $server;
// openConnectionToServer($server)
}
public function log($message) {
// log to a general log table
printf("[%s %s] %s\n", $this->server, date("Y-m-d H:i:s"), $message);
}
public function error($message) {
// log to a general log table as well as error table
$this->log(sprintf('ERROR: %s', $message));
}
public function info($message) {
$this->log(sprintf('INFO: %s', $message));
}
}
For this example I’m simply outputting log information to the screen for the sake of simplicity, but in a real application we would connect to the database and send queries to insert data into log tables.
<?php
class STDOutLogger implements iLogger {
public function log($message) {
printf("[%s] %s\n", date("Y-m-d H:i:s"), $message);
}
public function error($message) {
$this->log(sprintf('ERROR: %s', $message));
}
public function info($message) {
$this->log(sprintf('INFO: %s', $message));
}
}
Logging to stdOut is simple as well. It simply adds some additional information (e.g. log time) and outputs it.
Then let’s create our Context
class (in this case - Log
class). It should dynamically accept a logger and delegate it all logging operations:
<?php
class Log {
public function __construct(iLogger $logger) {
$this->logger = $logger;
}
public function log($message) {
$this->logger->log($message);
}
public function error($message) {
$this->logger->error($message);
}
public function info($message) {
$this->logger->info($message);
}
public function setLogger(iLogger $logger) {
$this->logger = $logger;
}
}
As you can see we simply store the supplied logger instance and delegate all logging operations to it. If at any point we decide to switch to a different logger - we can simply call setLogger
with an instance of another logger and dynamically swap to it.
And lastly, the example usage code:
<?php
require('iLogger.php');
require('Log.php');
require('STDOutLogger.php');
require('DatabaseLogger.php');
// Create a new Log instance and configure it to use logging to stdOut strategy
$log = new Log(new STDOutLogger());
// Log some example information
$log->info('Logging to STDOut may be useful for testing');
$log->error('This is an error');
$log->log('Let\'s try swapping to a DatabaseLogger');
// During runtime we decide to start logging information to the database.
// We can simply set the new logger and continue logging
$log->setLogger(new DatabaseLogger('tmpserver'));
$log->log('Now our logger should be logging to the database');
$log->error('Logging error to the database');
Now if we run our example code with php5 main.php
, we should see the output:
% php5 main.php
[2014-03-16 21:56:28] INFO: Logging to STDOut may be useful for testing
[2014-03-16 21:56:28] ERROR: This is an error
[2014-03-16 21:56:28] Let's try swapping to a DatabaseLogger
Opening connection to the database server tmpserver...
[tmpserver 2014-03-16 21:56:28] Now our logger should be logging to the database
[tmpserver 2014-03-16 21:56:28] ERROR: Logging error to the database
As you can see it started by logging information to stdOut. At some point we decided to swap to a DatabaseLogger
. We’ve instantiated it, dynamically passed it to our Log
instance and it continued logging information without any problems.
Last thoughts
Strategy is a good design pattern to know. It provides a great deal of flexibility by allowing to dynamically change the behaviour while keeping algorithms separate making the code clearer. However, it sometimes requires more effort to implement and use.
You can find the code above as well as other design patterns I’ve talked about on my GitHub repository here: Strategy Design Pattern
These series on design patterns is something I’ll write on more in the future as I explore more. I hope you find these as useful as I do. If you have any questions or comments - let me know.
Subscribe via RSS