Bedrock Strategy Pattern: The Family of Algorithms

AlgorithmFamilyOrganizing Algorithms

Working through Larry Ullman’s PHP Advanced and Object-Oriented Programming (3rd Ed) book with the Boston PHP Percolator group, we came to an example of the Strategy pattern, and I decided to re-write it. I was able to preserve Larry’s sort algorithms, but I took a different tact for the rest of the pattern. One of my favorite features of the Strategy pattern is that the Gang of Four note that it removes the need for conditional statements. Oh boy! No conditional statements!

You may well wonder why not having conditional statements is a good thing. Simple. Without conditional statements in a program, it’s easier to update and change it. If you’ve ever written a big program and you make changes, you have to go through all of the conditional statements and make sure that needed changes have not upset the coding apple cart. (The State design pattern is another one that has no conditional statements.) This does not mean that design patterns are anti-conditonal statements, but they can get in the way when making changes and updates. Besides, only the Strategy and State patterns are mentioned as having the advantage of no conditional statements. The client makes a request, and the pattern goes straight to requested property or operation with no if’s or switches. After all, if a client wants an algorithm, why should it have to go through a conditional series if it knows what it wants? (If the client is not sure what it needs, a Chain of Responsibility pattern, that does have conditional statements, is used. However, all the conditionals are pretty much the same and just check to see if the request has to be passed along the chain.) To get started take a look at the generated output and download the files:
PlayDownload

Context class and Strategy interface

Larry Ullman’s idea of using different sort algorithms is a perfect example of a “family of algorithms.” So when creating this particular Strategy pattern, I kept the same algorithms, but I removed the conditionals from the concrete strategies. To get started you can take a look at the formal class diagram in the first PHP Strategy pattern placed on this blog. After taking a look at it, consider Figure 1 that shows the general files used and their relationship to one another:

Figure 1: File Diagram of Strategy Pattern

Figure 1: File Diagram of Strategy Pattern

We begin with the Context class. It has three features (GoF 317):

  1. It is configured with a ConcreteStrategy object.
  2. It maintains a reference to a Strategy object.
  3. It may define an interface that lets Strategy access its data.

First, look at the following Context class. The first thing to note is that it is not an abstract class nor an interface. See if you can recognize the three elements the the Context participant listed above:


class Context 
{
    private $strategy;
 
    public function __construct(IStrategy $strategy) 
        {
        $this->strategy = $strategy;
    }
 
    public function algorithm(Array $elements) 
        {
        $this->strategy->algorithm($elements);
    }
}
?>

You can see that the constructor function calls for a ConcreteStrategy. We know that because the type hinting shows that the data type must be IStrategy. Looking ahead, you find that IStrategy is an interface; not a concrete class so you may think, “That’s wrong!” However, PHP type hinting allows the data type to be a child of the named type. (The same is true for strongly typed languages like C# and Java as well.) Further, a principle of design patterns is to program to the interface and not the implementation. Besides, you cannot create an instance of an interface or abstract class anyway, so the type would have to be a concrete implementation of the interface (IStrategy, which is the Strategy participant in this example).

Second, it maintains a reference to the Strategy object (IStrategy in the example). The private variable $strategy is assigned a Strategy instance. Again we know that because the type hinting requires an IStrategy type.

Third, the class defines an interface that gives Strategy access to its data in the algorithm() method. Note that the $strategy property is part of algorithm() method that has been instantiated through the IStrategy type hinted argument passed in constructor.

The Strategy participant in the design pattern is an interface, IStrategy. It is nothing more than a method expecting an array as an argument. The individual concrete strategy classes instantiate the method in any way they want, as long as the argument is an array.


interface IStrategy 
{
    public function algorithm(Array $elements);
}
?>

Think of the algorithm() method as a way for the Strategy to access the data.

Sort Strategies for an Array

To set up sort algorithms for an array, each of the four types of sorts (Alpha ascending and descending, and MultiNumber ascending and descending) as well as a non-sort array reading algorithm to display the original array are to be encapsulated as a ‘family of algorithms’ implementing a Strategy interface (IStrategy). As you can see in the following five classes, each implements the IStrategy interface. These will be called through the Context to meet the Client’s request. (No conditional statements allowed!)


// NoSort.php
include_once('IStrategy.php');         
class NoSort implements IStrategy 
{     
    private $collection;
        public function algorithm(array $elements) 
        {     
                echo "No Sort";
                $this->collection=$elements;
                echo '
    '; foreach ($this->collection as $student) { echo "
  1. {$student['name']} {$student['grade']}
  2. "
    ; } echo '
'
; } } ?>   // AlphaSort.php include_once('IStrategy.php'); class AlphaSort implements IStrategy { private $_index ='name'; public function algorithm(array $elements) { echo "Alpha Ascend Sort"; $this->collection=$elements;   uasort($this->collection, array($this, 'ascSort'));   echo '
    '; foreach ($this->collection as $student) { echo "
  1. {$student['name']} {$student['grade']}
  2. "
    ; } echo '
'
;   }   private function ascSort($x, $y) { return strcasecmp($x[$this->_index], $y[$this->_index]); } } ?>   // AlphaSortDes.php include_once('IStrategy.php'); class AlphaSortDes implements IStrategy { private $_index ='name'; public function algorithm(array $elements) { echo "Alpha Descend Sort"; $this->collection=$elements;   uasort($this->collection, array($this, 'descSort'));   echo '
    '; foreach ($this->collection as $student) { echo "
  1. {$student['name']} {$student['grade']}
  2. "
    ; } echo '
'
;   }   private function descSort($x, $y) { return strcasecmp($y[$this->_index], $x[$this->_index]); } } ?>   //MultiNumber.php include_once('IStrategy.php'); class MultiNumber implements IStrategy { private $_index ='grade'; public function algorithm(array $elements) { echo "Multi Number Ascend Sort"; $this->collection=$elements;   uasort($this->collection, array($this, 'ascSort'));   echo '
    '; foreach ($this->collection as $student) { echo "
  1. {$student['name']} {$student['grade']}
  2. "
    ; } echo '
'
; }   private function ascSort($x, $y) { return strcasecmp($x[$this->_index], $y[$this->_index]); } } ?>   //MultiNumberDes.php include_once('IStrategy.php'); class MultiNumberDes implements IStrategy { private $_index ='grade'; public function algorithm(array $elements) { echo "Multi Number Descend Sort"; $this->collection=$elements;   uasort($this->collection, array($this, 'descSort'));   echo '
    '; foreach ($this->collection as $student) { echo "
  1. {$student['name']} {$student['grade']}
  2. "
    ; } echo '
'
;   }   private function descSort($x, $y) { return strcasecmp($y[$this->_index], $x[$this->_index]); } } ?>

Each of the sort algorithms is pretty simple, but that’s okay. Suppose you develop a big hairy sort algorithm that would choke a python? You could just add a concrete implementation or change an existing concrete strategy, and as long as it implemented the IStrategy interface, it’d work just fine. The larger and more complex the program, the more value you will see in design patterns. That’s because changes in large complex programs are more likely to cause major problems if they are not cast as design patterns.

The Client

Finally, we can look at the Client. The Client makes requests through the Context, and the Context forwards the request to the desired strategy. We slipped in the algorithm() method giving access


include_once('Context.php');
include_once('NoSort.php');
include_once('AlphaSort.php');
include_once('AlphaSortDes.php');
include_once('MultiNumber.php');
include_once('MultiNumberDes.php');
include_once('IStrategy.php');         
 
class Client 
{     
        private $context;
        private $collection=array();
 
        public function __construct()
        {
                //Set up the style through an HTML document object
                echo $this->getStyle();
 
                //Get the array to sort
                $this->collection= $this->getArray();
 
                //#A Instantiate a Context object with a concrete strategy as an argument
                $this->context = new Context(new NoSort());
 
                //#B Use the algorithm method to specify the array to be sorted
                $this->context->algorithm($this->collection);
 
                //Request different strategies using #A and #B above
                $this->context = new Context(new AlphaSort());
                $this->context->algorithm($this->collection);
                $this->context = new Context(new AlphaSortDes());
                $this->context->algorithm($this->collection);
                $this->context = new Context(new MultiNumber());
                $this->context->algorithm($this->collection);
                $this->context = new Context(new MultiNumberDes());
                $this->context->algorithm($this->collection);
        }
 
        //Build array
        private function getArray()
        {
                $students = array(
                256 => array('name' => 'Mike', 'grade' => 98.5),
                2 => array('name' => 'Tom', 'grade' => 97.1),
                9 => array('name' => 'Brian', 'grade' => 96.0),
                364 => array('name' => 'Rick', 'grade' => 95.1),
                48 => array('name' => 'Alex', 'grade' => 94.1),
                68 => array('name' => 'Bill', 'grade' => 42.6)
                );
                return $students;
        }
 
        ////Heredoc to add style
        private function getStyle()
        {
                $hstuff=<<
                
                   Strategy
                   
                
                
                

Bedrock Strategy

HSTUFF
; return $hstuff; } } $worker=new Client(); ?>

Here’s the CSS in case you want your screen to look like the example.

@charset "UTF-8";
/* CSS Document */
/*705B35,C7B07B,E8D9AC,FFF6D9,570026*/
body
{
        margin-left:20px;
        margin-right:20px;
        background-color:#FFF6D9;
        color:#570026;
        font-family:Verdana, Geneva, sans-serif;
}
 
a
{
        text-decoration:none;
        color:#705B35;
}
 
a:hover
{
        color:#C7B07B;
}
 
h3
{
        font-family:Tahoma, Geneva, sans-serif;
        background-color:#570026;
        color:#E8D9AC;
        text-indent:1em;
}
 
h1
{
        font-family:"Arial Black", Gadget, sans-serif;
        color:#705B35;
        text-align:center;
}
 
.special
{
        color:#cc0000;
}

The Versatile Strategy

For me, the Strategy is one of my core design patterns. By core, I mean it’s one that I find myself using for a lot of the kinds of problems PHP developers deal with. I became enamored with it when I had a customer for whom I wrote a PHP program that involved a table with 105 fields. I had used the Strategy pattern for data entry, data viewing, updating data, deleting records, and data searching. The same customer gave me an entirely different project with a different table, and the whole thing had to work on an iPad tablet. After very few alterations, I was able to re-use almost all of the structure. The particulars changed, but knowing that the Client class was loosely bound to the Strategy participants, I could make changes as long as I complied with the interface.

So if you have a change to give the Strategy a whirl, do so. You’ll be glad to you did.

Copyright © 2013 William Sanders. All Rights Reserved.

0 Responses to “Bedrock Strategy Pattern: The Family of Algorithms”


  • No Comments

Leave a Reply