PHP Advanced and Object-Oriented Programming (2013)
Visual Quickpro Guide
7. Design Patterns
In This Chapter
Understanding Design Patterns
The Singleton Pattern
The Factory Pattern
The Composite Pattern
The Strategy Pattern
Review and Pursue
Once you begin regularly programming using objects, it’s not too long before you encounter the subject of design patterns. A design pattern is, simply put, a recommended best practice for solving a particular problem. In other words, if you’re trying to figure out how to implement such-and-such functionality, then use this design pattern as your approach.
This chapter introduces the concept of design patterns and walks you through four commonly used ones. Be forewarned that design patterns can be rather abstract, especially if the entire subject of OOP is still new to you.
Understanding Design Patterns
In the introduction, I state that a design pattern is a best practice for solving a particular problem. Before getting into some actual patterns, let’s look at what this definition means in more detail.
You may be surprised to learn that the first important book on design patterns was not a programming book at all, but one on architecture and city planning: A Pattern Language: Towns, Buildings, Construction by Christopher Alexander, Sara Ishikawa, and Murray Silverstein (Oxford University Press, 1977). This fact goes toward the first design pattern quality to grasp: design patterns are not specific bits of code that you can copy and paste as needed. No, design patterns are programming approaches, where the specific code—the implementation of that approach—may change from one situation to the next.
Second, a design pattern doesn’t just define a solution, but also the problem the solution is trying to address. One of the hardest things about learning design patterns is understanding which design pattern applies to a given situation.
But a design pattern is not made up of just a problem and a solution, but rather four key pieces:
• Its name
• A discussion of the problem: under what circumstances you would use the particular pattern
• The solution, which is not concrete in terms of code, but provides enough information for you to be able to reliably write the code
• The consequences: the pros and cons of the particular pattern
This last piece represents an important aspect of good software design. Identifying the consequences of an approach reflects the fact that while there may be best practices, there is no perfect solution for all possible situations. Understanding not just the problem and solution, but also the consequences will help you know when and if you should use a particular design pattern.
The rest of the chapter will introduce and demonstrate four design patterns. All of these were first defined in the “Gang of Four” book (see the sidebar). Before moving on, however, I want to add that design patterns, like OOP in general, are prone to being overly praised. Design patterns, like OOP, are a great tool to have in your developer toolbox. But neither design patterns nor OOP provides a magic bullet for all possible situations. As with any type of programming, the goal is to select the right tool for the job.
The Gang of Four
The seminal programming book on the subject of design patterns is Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Professional, 1994). You’ll see this group of authors referred to as the “Gang of Four,” abbreviated as GoF.
The Design Patterns book identified 23 patterns, organized into three broad categories: creational, structural, and behavioral. The book primarily uses C++ for its examples, along with Smalltalk, but the whole point of design patterns is that they define approaches, regardless of the language in use.
Creational patterns create objects, saving you from having to do so manually in your code. The Builder, Factory, Prototype, and Singleton patterns are all creational; Factory and Singleton are covered in this chapter.
Structural patterns assist in the creation and use of complex structures. Examples of structural patterns include Adapter, Bridge, Composite (covered in this chapter), Decorator, Façade, and Proxy.
Behavioral patterns address how objects within a system communicate and how a program’s logic flows. The Command, Iterator, Observer, State, Strategy (covered in this chapter), and Template Method patterns are all behavioral.
As a second caveat, entire books have been written on design patterns. This one chapter is meant to be an introduction to the concept—providing what you need to get going. I’ve selected a range of patterns that will best demonstrate the variety of possible design patterns while sticking to those patterns that are easiest to grasp. I’ve also forgone the formality of covering each pattern’s consequences, as I feel doing so would have just added more words and things for you to think about, thereby making the learning that much harder.
The goal in this chapter is for you to get a handle on what design patterns are, how they are used, and what types of design patterns exist. Once you are comfortable with all that, try learning one new pattern at a time, using the Gang of Four book and online articles as resources. Take copious notes, and try not to move on to another pattern until you fully comprehend the one you are studying.
Tip
Some of these examples use additions to PHP’s OOP model as recent as PHP 5.3 and PHP 5.4. If you see any weird errors while running an example, it’s likely because you’re not using a current enough version of PHP.
Tip
As is the case with the Normal Forms in database design, you’ll see specific design patterns described in slightly different ways, often accenting one aspect of the problem or solution over another. Along with time and practice, it may take reading several different sources before you best comprehend the true heart of a particular pattern: problem and solution.
Tip
Being comfortable with design patterns makes project collaboration easier, as design patterns provide a vocabulary for common approaches.
The Singleton Pattern
The Singleton pattern is a creational pattern that will restrict an application to creating only a single instance of a particular class type. For example, a Web site will need a database connectivity object, but should have only one (you’d almost always want all database interactions going through a single connection), so you could use Singleton to enforce that restriction.
In terms of design, it’s relatively easy to implement the Singleton pattern . For starters, you can use a static attribute to guarantee that only one instance of a particular class exists.
The simple UML representation of the Singleton pattern. (See the previous three chapters for more on UML symbols.)
class SomeClass {
static private $_instance = NULL;
}
As explained in Chapter 5, “Advanced OOP,” because the attribute is static, it’s shared by all instances of the class. Taking that idea just one step further, if you store the actual instance in that attribute (i.e., assign an object to it), then all references to this class can use that attribute.
The next step is to create a method that will create an instance of the class if one does not exist and return the instance regardless (assuming the name of the class is SomeClass):
class SomeClass {
static private $_instance = NULL;
static function getInstance() {
if (self::$_instance == NULL) {
self::$_instance = new SomeClass();
}
return self::$_instance;
}
}
It’s common for a Singleton to name this method getInstance(). In the method, the conditional checks if the $_instance attribute still has a NULL value. If so, a new instance is created and assigned to the attribute. Finally, the instance is returned.
Now the class can be used in this manner:
$obj1 = SomeClass::getInstance();
If this is the first object in the application of that type, the instance will be created, assigned to the internal private attribute, and then returned.
Script 7.1. The Config class implements the Singleton pattern so that an entire Web application can make use of the same configuration object.
1 <?php # Script 7.1 - Config.php
2 // This page defines a Config class which uses the Singleton pattern.
3
4 /* The Config class.
5 * The class contains two attributes: $_instance and $settings.
6 * The class contains four methods:
7 * - _ _construct()
8 * - getInstance()
9 * - set()
10 * - get()
11 */
12 class Config {
13
14 // Store a single instance of this class:
15 static private $_instance = NULL;
16
17 // Store settings:
18 private $_settings = array();
19
20 // Private methods cannot be called:
21 private function _ _construct() {}
22 private function _ _clone() {}
23
24 // Method for returning the instance:
25 static function getInstance() {
26 if (self::$_instance == NULL) {
27 self::$_instance = new Config();
28 }
29 return self::$_instance;
30 }
31
32 // Method for defining a setting settings:
33 function set($index, $value) {
34 $this->_settings[$index] = $value;
35 }
36
37 // Method for retrieving a setting:
38 function get($index) {
39 return $this->_settings[$index];
40 }
41
42 } // End of Config class definition.
When a second object also calls that code, the same instance will be returned:
$obj2 = SomeClass::getInstance();
Now both $obj1 and $obj2 refer to the same instance of ClassName.
There is one catch, however. If a user tries to create a new object of that class type using new or clone, you would end up with multiple instances, defeating the purpose of a Singleton. The trick to preventing that from happening is to make a private constructor that does nothing:
private function _ _construct() {}
Now the following code will trigger an error :
$obj = new ClassName();
Because the class’s constructor is private, you cannot use new to create an object of this type.
Another good use for a Singleton is to create one global object, such as a configuration object used by an entire site. Let’s do that in the next series of steps.
To create a Singleton class
1. Begin a new PHP script in your text editor or IDE, to be named Config.php (Script 7.1):
<?php # Script 7.1 - Config.php
2. Start defining the Config class:
class Config {
Note that this is not an abstract class, as an instance of this class will be created.
3. Declare the two private attributes:
static private $_instance = NULL;
private $_settings = array();
The first attribute, $_instance, will represent a single instance of the class. The second attribute, $_settings, will store all the configuration settings. Both are initialized here.
4. Define a private constructor and a private _ _clone() method:
private function _ _construct() {}
private function _ _clone() {}
This code prevents the class from being instantiated using new or clone.
5. Define the getInstance() method:
static function getInstance() {
if (self::$_instance == NULL) {
self::$_instance = new Config();
}
return self::$_instance;
}
This method is defined using the code already explained.
6. Define the set() method:
function set($index, $value) {
$this->_settings[$index] = $value;
}
This method takes two arguments: a setting name, or index, and its value. The two arguments are used to manipulate the $_settings array.
7. Define the get() method:
function get($index) {
return $this->_settings[$index];
}
The get() method is used to return a current setting. After reading Chapter 8, “Using Existing Classes,” you’d likely want to have this method throw an exception should the function not be provided with an index that matches a previously stored value.
8. Complete the class:
} // End of Config class definition.
9. Save the file as Config.php and place it in your Web directory.
To use the Config class
1. Begin a new PHP script in your text editor or IDE, to be named singleton.php, starting with the HTML (Script 7.2):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Singleton</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Using a Singleton Config Object</h2>
<?php # Script 7.2 - singleton.php
2. Load the class definition:
require('Config.php');
Script 7.2. Thanks to the Singleton pattern, multiple variables will always reference the same object.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Singleton</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <h2>Using a Singleton Config Object</h2>
10 <?php # Script 7.2 - singleton.php
11 // This page uses the Config class (Script 7.1).
12
13 // Load the class definition:
14 require('Config.php');
15
16 // Create the object:
17 $CONFIG = Config::getInstance();
18
19 // Set some value:
20 $CONFIG->set('live', 'true');
21
22 // Confirm the current value:
23 echo '<p>$CONFIG["live"]: ' . $CONFIG->get('live') . '</p>';
24
25 // Create a second object to confirm:
26 $TEST = Config::getInstance();
27 echo '<p>$TEST["live"]: ' . $TEST->get('live') . '</p>';
28
29 // Delete the objects:
30 unset($CONFIG, $TEST);
31
32 ?>
33 </body>
34 </html>
3. Create a Config instance:
$CONFIG = Config::getInstance();
Remember that an instance is obtained through the static getInstance() method.
Frequently, an object which is a Singleton instance uses all capital letters (like a constant) for its name, although this is not required.
4. Establish a setting:
$CONFIG->set('live', 'true');
At this point, $CONFIG is an object of Config type, usable like any other object.
5. Print the setting’s value:
echo '<p>$CONFIG["live"]: ' . $CONFIG->get('live') . '</p>';
6. Create another configuration object and confirm the set value:
$TEST = Config::getInstance();
echo '<p>$TEST["live"]: ' . $TEST->get('live') . '</p>';
7. Complete the page:
unset($CONFIG, $TEST);
?>
</body>
</html>
8. Save the file as singleton.php, place it in your Web directory, and test in your Web browser .
Both variables refer to the same, lone class instance, thanks to the Singleton pattern.
Tip
In theory, code outside of any class could regulate the number of class instances. However, since you’re using OOP, and a single instance is a requirement of a class, it’d be best to design that functionality in the class.
The Factory Pattern
The Factory pattern is another creation pattern, like Singleton. But unlike Singleton, which creates and manages a single object of a single class type, the Factory pattern is used to manufacture potentially multiple objects of many different class types.
Of course, you already know how to create objects of a specific type:
$obj = SomeClass();
So why would you need a Factory to do that for you?
The Factory pattern becomes useful in situations where the type of object that needs to be generated isn’t known when the program is written but only once the program is running. In very dynamic applications, this can often be the case.
Another clue for when the Factory pattern might be appropriate is when there’s an abstract base class, and different derived subclasses will need to be created on the fly. This particular design structure is important with the Factory pattern, as once you’ve created the object, regardless of its specific type, the use of that object will be consistent.
The Factory pattern works via a static method, conventionally named Create(), factory(), factoryMethod(), or createInstance(). The method takes at least one argument, which indicates the type of object to create. The method then returns an object of that type :
static function Create($type) {
// Validate $type.
return new SomeClassType();
}
In the Factory pattern, the abstract Factory base class is extended by concrete Factory classes that will output objects of that class type (aka, products).
That’s the basic idea. Let’s see how this will be implemented in the following example, which will create a different type of Shape based on input provided to the page through the URL. This example will use the Shape, Rectangle, and Triangle classes from Chapter 6, “More Advanced OOP.” You will need to copy those to the same directory as the following files. Also, you can either also copy over the tDebug.php file, or remove that trait reference from the Rectangle class (see Chapter 6).
To create a Factory
1. Begin a new PHP script in your text editor or IDE, to be named ShapeFactory.php (Script 7.3):
<?php # Script 7.3 - ShapeFactory.php
2. Start defining the ShapeFactory class:
abstract class ShapeFactory {
The Factory class will be abstract, just as Shape itself is. You’ll never create an instance of the ShapeFactory class.
The class has no attributes.
3. Start defining the static method:
static function Create($type, array $sizes) {
This static method will take two arguments: the type of shape to create and an array of sizes. For a rectangle, that array would contain two values; for a triangle, three; and for a circle, only one (the radius).
Type hinting is used to enforce the requirement that $sizes be an array.
4. Create a different object based on the type value:
switch ($type) {
case 'rectangle':
return new Rectangle ($sizes[0], $sizes[1]);
break;
case 'triangle':
return new Triangle($sizes[0], $sizes[1], $sizes[2]);
break;
} // End of switch.
The switch checks the value of $type against the expected values. For now, just these two Shape types are recognized.
Script 7.3. The ShapeFactory class generates different Shape-derived objects on the fly.
1 <?php # Script 7.3 - ShapeFactory.php
2 // This page defines a ShapeFactory class which uses the Factory pattern.
3
4 /* The ShapeFactory class.
5 * The class contains no attributes.
6 * The class contains one method: Create().
7 */
8 abstract class ShapeFactory {
9
10 // Static method that creates objects:
11 static function Create($type, array $sizes) {
12
13 // Determine the object type based upon the parameters received.
14 switch ($type) {
15 case 'rectangle':
16 return new Rectangle($sizes[0], $sizes[1]);
17 break;
18 case 'triangle':
19 return new Triangle($sizes[0], $sizes[1], $sizes[2]);
20 break;
21 } // End of switch.
22
23 } // End of Create() method.
24
25 } // End of ShapeFactory class.
Again, a more complete version of this class would throw an exception if improper values were provided.
5. Complete the method and the class:
} // End of Create() method.
} // End of ShapeFactory class.
6. Save the file as ShapeFactory.php and place it in your Web directory.
To use the ShapeFactory class
1. Begin a new PHP script in your text editor or IDE, to be named factory.php, starting with the HTML (Script 7.4):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Factory</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 7.4 - factory.php
2. Load the class definitions:
require('ShapeFactory.php');
require('Shape.php');
require('Triangle.php');
require('Rectangle.php');
This script needs access to any possible Shape-derived class that might be used, as well as the Shape class definition itself, and ShapeFactory.
In the next chapter, you’ll learn how to establish an autoloader, so that the required class files can be included only when needed.
3. Perform some minimal validation:
if (isset($_GET['shape'], $_GET['dimensions'])) {
To make this example shorter, I’ve forgone the HTML form that the user might fill out to create different shapes and will just take values straight from the URL. The URL needs to include both a shape value and a dimensions value.
4. Create the object:
$obj = ShapeFactory::Create ($_GET['shape'], $_GET['dimensions']);
To create the object, the static method of the ShapeFactory class is invoked, passing it the values that came from the URL.
In a more realized version of this script, I’d add error handling here to confirm that the shape object was created before attempting to use it in the following steps.
5. Print an introduction:
echo "<h2>Creating a {$_GET['shape']}...</h2>";
6. Print the area:
echo '<p>The area is ' . $obj->getArea() . '</p>';
As $obj is now some sort of Shape-based object, you know it has a getArea() method that can be called.
7. Print the perimeter:
echo '<p>The perimeter is ' . $obj->getPerimeter() . '</p>';
Script 7.4. This script will create and use different types of shapes based on values passed in the URL.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Factory</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 7.4 - factory.php
10 // This page uses the ShapeFactory class (Script 7.2).
11
12 // Load the class definitions:
13 require('ShapeFactory.php');
14 require('Shape.php');
15 require('Triangle.php');
16 require('Rectangle.php');
17
18 // Minimal validation:
19 if (isset($_GET['shape'], $_GET['dimensions'])) {
20
21 // Create the new object:
22 $obj = ShapeFactory::Create($_GET['shape'], $_GET['dimensions']);
23
24 // Print a little introduction:
25 echo "<h2>Creating a {$_GET['shape']}...</h2>";
26
27 // Print the area:
28 echo '<p>The area is ' . $obj->getArea() . '</p>';
29
30 // Print the perimeter:
31 echo '<p>The perimeter is ' . $obj->getPerimeter() . '</p>';
32
33 } else {
34 echo '<p class="error">Please provide a shape type and size.</p>';
35 }
36
37 // Delete the object:
38 unset($obj);
39
40 ?>
41 </body>
42 </html>
8. Complete the minimal validation:
} else {
echo '<p class="error">Please provide a shape type and size.</p>';
}
9. Complete the page:
unset($obj);
?>
</body>
</html>
10. Save the file as factory.php, place it in your Web directory, along with the other class files, and test in your Web browser .
Based on values passed in the URL, this script reports the area and perimeter of a rectangle.
You’ll need to use a URL like factory.php?shape=rectangle& dimensions[]=10&dimensions[]=14.
11. Provide new parameters in the URL and retest in your Web browser .
Just changing the URL parameters, without touching the underlying code, now reports on a triangle.
Tip
The Factory pattern is also meant to be easily extendible (i.e., supporting new classes as they are defined).
Tip
One of the consequences of using the Factory pattern is that the Factory class is very tightly coupled with the rest of the application, due to its internal validation. You can see this in the ShapeFactory Create() method, which makes assumptions about the incoming $sizes array.
Tip
A variation on the Factory pattern is Abstract Factory. Whereas the Factory pattern outputs different objects, all derived from the same parent, the Abstract Factory outputs other factories.
The Composite Pattern
The Singleton and Factory patterns are creational: used to generate one or more objects. Another category of patterns is structural. These patterns apply in situations where a nontraditional class structure, or a modification of an existing class structure, is required. For an example of a structural pattern, I want to introduce Composite.
The focus in Chapter 5 is on inheritance as a way of basing one class definition on another. In Chapter 6, I talk about composition, which is another design approach. The composition explained there involves one class type (e.g., Department) being composed of other class types (e.g.,Employees), which will commonly happen. Similarly, the Composite pattern applies in situations where you have an object that might represent a single entity or a composite entity, but still needs to be usable in the same manner.
For example, an HTML form contains one or more form elements. From a programming perspective, certain behaviors will apply to both the entire form and its individual components:
• Display
• Validate
• Show errors
Addressing these needs without using patterns would require that you replicate a lot of code in two different classes (i.e., Form and FormElement). And, as you should know by now, replication makes for bad programming. The solution to this problem is to apply Composite so that the entire form or just an individual component can be treated the same. For example, you could show errors on the form or on a single element; you could validate the entire form at once or just a single element (e.g., validate the availability of a username via Ajax).
Another sign that the Composite pattern may apply is when you’re working with a tree-like structure. For example, the whole form is the root of the tree, and the different form elements would be the branches (often called “leaves” in Composite jargon).
To implement the Composite pattern, you’ll normally start with an abstract base class that will be extended by the different subclasses. The base class needs to identify the methods for adding and removing “leaves” (or composite items). The base class also needs to identify any functionality that the composite, or its subelements, needs to do:
abstract class FormComponent {
abstract function add (FormComponent $obj);
abstract function remove (FormComponent $obj);
abstract function display();
abstract function validate();
abstract function showError();
}
As you can see, the abstract class here is also using type hinting (see Chapter 6). The first two methods are key to Composite. The other three represent the specific functionality needed by this particular example.
Each subclass now inherits from this derived class. By definition, each must also define the implementation of the abstract methods. Here’s a partial example:
class Form extends FormComponent {
private $_elements = array();
function add(FormComponent $obj) {
$this->_elements[] = $obj;
}
function display() {
// Display the entire form.
}
}
class FormElement extends FormComponent {
function add(FormComponent $obj) {
return $obj; // Or false.
}
function display() {
// Display the element.
}
}
As you can see in that code, the Form class implements a true add() method, which allows you to add elements to a form:
$form = new Form();
$email = new FormElement();
$form->add($email);
Note that the FormElement class still has to define the add() method, but the method shouldn’t do anything, because you don’t add subelements to that class type. Instead, such add methods will conventionally return the object that’s being added, or false, or throw an exception.
With these class definitions, you can now treat entire forms or individual form elements in the same manner.
To demonstrate another implementation of the Composite pattern, let’s create a variation on the Department-Employee example. Here, there will be two types of work units: employees and teams, with the latter being made up of employees. Jobs to be done can be assigned to, and completed by, either.
To create a Composite design
1. Begin a new PHP script in your text editor or IDE, to be named WorkUnit.php (Script 7.5):
<?php # Script 7.5 - WorkUnit.php
2. Start defining the WorkUnit class:
abstract class WorkUnit {
Again, this is an abstract class, because you won’t create objects of this class type.
3. Declare two protected attributes:
protected $tasks = array();
protected $name = NULL;
These two attributes will be used by any class that extends this one. The first attribute stores the jobs to be done. The second stores the name of the current object, be it a team or an employee.
4. Define the constructor:
function _ _construct($name) {
$this->name = $name;
}
The constructor is not abstract; it’s fully implemented and assigns the name of the team or employee to the protected attribute.
Script 7.5. The WorkUnit abstract class is extended by two concrete classes to implement the Composite pattern.
1 <?php # Script 7.5 - WorkUnit.php
2 // This page defines a WorkUnit class which uses the Composite pattern.
3 // This page also defines Team and Employee classes, which extend WorkUnit.
4
5 /* The WorkUnit class.
6 * The class contains two attributes: $tasks and $name.
7 * The class contains five methods: _ _construct(), getName(), add(), remove(), assignTask(), and completeTask().
8 */
9 abstract class WorkUnit {
10
11 // For storing work to be done:
12 protected $tasks = array();
13
14 // For storing the employee or team name:
15 protected $name = NULL;
16
17 // Constructor assigns the name:
18 function _ _construct($name) {
19 $this->name = $name;
20 }
21
22 // Method that returns the name:
23 function getName() {
24 return $this->name;
25 }
26
27 // Abstract functions to be implemented:
28 abstract function add(Employee $e);
29 abstract function remove(Employee $e);
30 abstract function assignTask($task);
31 abstract function completeTask($task);
32
33 } // End of WorkUnit class.
34
35 /* The Team class extends WorkUnit.
36 * The class has one new attribute: $_employees.
37 * The class has one new method: getCount().
38 */
39 class Team extends WorkUnit {
40
41 // For storing team members:
42 private $_employees = array();
43
44 // Implement the abstract methods...
45 function add(Employee $e) {
46 $this->_employees[] = $e;
47 echo "<p>{$e->getName()} has been added to team {$this->getName()}.</p>";
48 }
49 function remove(Employee $e) {
50 $index = array_search($e, $this->_employees);
51 unset($this->_employees[$index]);
52 echo "<p>{$e->getName()} has been removed from team {$this->getName()}.</p>";
53 }
54 function assignTask($task) {
55 $this->tasks[] = $task;
56 echo "<p>A new task has been assigned to team {$this->getName()}. It should be easy to do with {$this->getCount()} team member(s).</p>";
57 }
58 function completeTask($task) {
59 $index = array_search($task, $this->tasks);
60 unset($this->tasks[$index]);
61 echo "<p>The '$task' task has been completed by team {$this->getName()}.</p>";
62 }
63
64 // Method for returning the number of team members:
65 function getCount() {
66 return count($this->_employees);
67 }
68
69 } // End of Team class.
70
71 /* The Employee class extends WorkUnit.
72 * The class has no new attributes or methods.
73 */
74 class Employee extends WorkUnit {
75
76 // Empty functions:
77 function add(Employee $e) {
78 return false;
79 }
80 function remove(Employee $e) {
81 return false;
82 }
83
84 // Implement the abstract methods...
85 function assignTask($task) {
86 $this->tasks[] = $task;
87 echo "<p>A new task has been assigned to {$this->getName()}. It will be done by {$this->getName()} alone.</p>";
88 }
89 function completeTask($task) {
90 $index = array_search($task, $this->tasks);
91 unset($this->tasks[$index]);
92 echo "<p>The '$task' task has been completed by employee {$this->getName()}.</p>";
93 }
94
95 } // End of Employee class.
5. Define the getName() method:
function getName() {
return $this->name;
}
This method is just a “getter” for the $name attribute. I created this method only for better output in the final script (i.e., to be able to see which employee or team is doing what), but it is not inherent to the Composite pattern approach.
6. Identify the abstract methods:
abstract function add (Employee $e);
abstract function remove (Employee $e);
abstract function assignTask ($task);
abstract function completeTask ($task);
For this example, I’m defining four abstract methods. Two are required by Composite: one for adding composite pieces (i.e., employees) and one for removing them. The other two methods are unique to this example and demonstrate the functionality that both specific classes can do.
7. Complete the class:
} // End of WorkUnit class.
8. Start defining the Team class:
class Team extends WorkUnit {
private $_employees = array();
The Team class extends WorkUnit, meaning that it inherits the constructor and getName() methods, as well as the two protected attributes. It adds a new attribute, which will store the “leaves.”
9. Implement the add() method:
function add(Employee $e) {
$this->_employees[] = $e;
echo "<p>{$e->getName()} has been added to team {$this->getName()}.</p>";
}
This method will be used to add composite members to the class. It uses type hinting to expect an Employee object, which is then added to the internal array. So that the script has something to show, a message reports the name of the employee added to the team.
10. Implement the remove() method:
function remove(Employee $e) {
$index = array_search($e, $this->_employees);
unset($this->_employees[$index]);
echo "<p>{$e->getName()} has been removed from team {$this->getName()}.</p>";
}
This method is for removing team members from the composite object. To do so, the index must first be found for the employee being removed. Then this element is unset from the array. In a more fleshed-out class, I’d check that $index has a positive value first.
Again, a message indicates what’s happening.
11. Define the assignTask() method:
function assignTask($task) {
$this->tasks[] = $task;
echo "<p>A new task has been assigned to team {$this->getName()}. It should be easy to do with {$this->getCount()} team member(s).</p>";
}
Here, a new job is added to the ongoing list of responsibilities for the team. A message indicates that the task has been added, and shows how many employees (i.e., members of this team) will be available to work on the task.
12. Define the completeTask() method:
function completeTask($task) {
$index = array_search($task, $this->tasks);
unset($this->tasks[$index]);
echo "<p>The '$task' task has been completed by team {$this->getName()}.</p>";
}
This method is similar to remove(), this time removing completed tasks.
13. Define the getCount() method and complete the class:
function getCount() {
return count ($this->_employees);
}
} // End of Team class.
This method simply returns the number of employees in the team.
14. Begin defining the Employee class:
class Employee extends WorkUnit {
function add(Employee $e) {
return false;
}
function remove(Employee $e) {
return false;
}
The Employee class also extends WorkUnit, so it must define the add() and remove() methods, even if both just return false.
15. Implement the two remaining abstract methods:
function assignTask($task) {
$this->tasks[] = $task;
echo "<p>A new task has been assigned to {$this->getName()}. It will be done by {$this->getName()} alone.</p>";
}
function completeTask($task) {
$index = array_search($task, $this->tasks);
unset($this->tasks[$index]);
echo "<p>The '$task' task has been completed by employee {$this->getName()}.</p>";
}
These two methods work similarly to those in Team, with a couple of employee-specific changes.
16. Complete the class:
} // End of Employee class.
17. Save the file as WorkUnit.php and place it in your Web directory.
Script 7.6. In this script, the Composite pattern is implemented in different types of work units.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Composite</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <h2>Using Composite</h2>
10 <?php # Script 7.6 - composite.php
11 // This page uses the WorkUnit, Team, and Employee classes (Script 7.5).
12
13 // Load the class definition:
14 require('WorkUnit.php');
15
16 // Create the objects:
17 $alpha = new Team('Alpha');
18 $john = new Employee('John');
19 $cynthia = new Employee('Cynthia');
20 $rashid = new Employee('Rashid');
21
22 // Assign employees to the team:
23 $alpha->add($john);
24 $alpha->add($rashid);
25
26 // Assign tasks:
27 $alpha->assignTask('Do something great.');
28 $cynthia->assignTask('Do something grand.');
29
30 // Complete a task:
31 $alpha->completeTask('Do something great.');
32
33 // Remove a team member:
34 $alpha->remove($john);
35
36 // Delete the objects:
37 unset($alpha, $john, $cynthia, $rashid);
38 ?>
39 </body>
40 </html>
To use the Composite classes
1. Begin a new PHP script in your text editor or IDE, to be named composite.php, starting with the HTML (Script 7.6):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Composite</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Using Composite</h2>
<?php # Script 7.6 - composite.php
2. Load the class definition:
require('WorkUnit.php');
3. Create the objects:
$alpha = new Team('Alpha');
$john = new Employee('John');
$cynthia = new Employee ('Cynthia');
$rashid = new Employee('Rashid');
Here are four different objects: one Team and three Employees.
4. Assign employees to the team:
$alpha->add($john);
$alpha->add($rashid);
5. Assign tasks:
$alpha->assignTask('Do something great.');
$cynthia->assignTask('Do something grand.');
Because WorkUnit uses the Composite pattern, all four objects can be treated the same from this point forward.
6. Complete a task and remove a team member:
$alpha->completeTask('Do something great.');
$alpha->remove($john);
This is just a test of the functionality of the class.
7. Complete the page:
unset($alpha, $john, $cynthia, $rashid);
?>
</body>
</html>
8. Save the file as composite.php, place it in your Web directory, and test in your Web browser .
The Composite pattern allows one class type to be used like another, even if one is composed of the other class type.
Tip
The Visitor pattern, which allows for operations across structures, is often used in conjunction with Composite.
Tip
This example could be extended so that teams could be made up of employees or other teams.
Tip
Another way of implementing this example using the Composite pattern would be to focus on the tasks. New tasks could be treated individually, or created as steps in other tasks. Then an individual task or a group of tasks could be assigned to work units.
Antipatterns
In learning about design patterns, you’ll often come across the term antipattern. A design pattern is a best practice; an antipattern is, therefore, an example of what we know did not work. As any developer comes to know, you learn as much, if not more, from your failures as from your successes.
The Strategy Pattern
The final pattern example to be explained in this chapter, Strategy, is a type of behavioral pattern. Behavioral patterns are used to address how an application runs. As a comparison, the Factory pattern can change the object type on the fly, but the Strategy pattern can change an algorithm on the fly (an algorithm just being a process or set of code used to perform a calculation or solve a problem). Strategy is most useful in situations where you have classes that may be similar, but not related, and differ only in their specific behavior.
For example, say you need a filtering system for strings. Different filters might include:
• Stripping out HTML
• Crossing out swear words
• Catching character combinations that can be used to send spam through contact forms and the like
The only quality that these three approaches have in common is that they’re all applied to strings. Other than that, there are no commonalities that might suggest they’d make sense as derived subclasses from a common base class.
Furthermore, these filters might be applied differently on the fly. For example, an application switch might indicate whether or not HTML or swear words are allowed in text. This, then, is a good candidate for the Strategy pattern.
First, start by defining an interface that dictates the needed functionality:
interface Filter {
function filter($str);
}
(For a refresher on interfaces, see Chapter 6.)
Specific filter types then implement their specific versions of the interface’s method:
class HtmlFilter implements Filter {
function filter($str) {
// Strip out the HTML.
return $str;
}
}
class SwearFilter implements Filter {
function filter($str) {
// Cross out swear words.
return $str;
}
}
Finally, another class would be written that could use any filter :
class FormData {
private $_data = NULL;
function _ _construct($input) {
$this->_data = $input;
}
function process(Filter $type) {
$this->_data = $type->filter($this->_data);
}
}
In the Strategy pattern, an interface is extended by more concrete classes, which in turn are used by a specific object (the Context).
In that code, the process() method expects to receive an object of type Filter through which the data would then be run.
And here’s how you would use this:
$form = new FormData($someUserInput);
if (/* No HTML allowed. */) {
$form->process(new HtmlFilter());
}
if (/* No swear words allowed. */) {
$form->process(new SwearFilter());
}
As an alternative use of Strategy, let’s take the multidimensional sort example from Chapter 1, “Advanced PHP Techniques,” and implement it using objects. To make the algorithms as reusable as possible, each algorithm will take constructor arguments to more finely tune its behavior.
Script 7.7. The iSort interface is implemented by two classes in order to create different sorting algorithms.
1 <?php # Script 7.7 - iSort.php
2 // This page defines an iSort interface and two classes.
3 // This page implements the Strategy pattern.
4
5 // The Sort interface defines the sort() method:
6 interface iSort {
7 function sort(array $list);
8 }
9
10 // The MultiAlphaSort sorts a multidimensional array alphabetically.
11 class MultiAlphaSort implements iSort {
12
13 // How to sort:
14 private $_order;
15
16 // Sort index:
17 private $_index;
18
19 // Constructor sets the sort index and order:
20 function _ _construct($index, $order = 'ascending') {
21 $this->_index = $index;
22 $this->_order = $order;
23 }
24
25 // Function does the actual sorting:
26 function sort(array $list) {
27
28 // Change the algorithm to match the sort preference:
29 if ($this->_order == 'ascending') {
30 uasort($list, array($this, 'ascSort'));
31 } else {
32 uasort($list, array($this, 'descSort'));
33 }
34
35 // Return the sorted list:
36 return $list;
37
38 }// End of sort() method.
39
40 // Functions that compares two values:
41 function ascSort($x, $y) {
42 return strcasecmp($x[$this->_index], $y[$this->_index]);
43 }
44 function descSort($x, $y) {
45 return strcasecmp($y[$this->_index], $x[$this->_index]);
46 }
47
48 } // End of MultiAlphaSort class.
49
50 // The MultiNumberSort sorts a multidimensional array numerically.
51 class MultiNumberSort implements iSort {
52
53 // How to sort:
54 private $_order;
55
56 // Sort index:
57 private $_index;
58
59 // Constructor sets the sort index and order:
60 function _ _construct($index, $order = 'ascending') {
61 $this->_index = $index;
62 $this->_order = $order;
63 }
64
65 // Function does the actual sorting:
66 function sort(array $list) {
67
68 // Change the algorithm to match the sort preference:
69 if ($this->_order == 'ascending') {
70 uasort($list, array($this, 'ascSort'));
71 } else {
72 uasort($list, array($this, 'descSort'));
73 }
74
75 // Return the sorted list:
76 return $list;
77
78 }// End of sort() method.
79
80 // Functions that compares two values:
81 function ascSort($x, $y) {
82 return ($x[$this->_index] > $y[$this->_index]);
83 }
84 function descSort($x, $y) {
85 return ($x[$this->_index] < $y[$this->_index]);
86 }
87
88 } // End of MultiNumberSort class.
To create a Strategy design
1. Begin a new PHP script in your text editor or IDE, to be named iSort.php (Script 7.7):
<?php # Script 7.7 - iSort.php
2. Define the iSort interface:
interface iSort {
function sort(array $list);
}
This interface is very simple, requiring only a single method: sort(). That method takes an array as its lone argument and will return the sorted version of the array (although the return value is not indicated in the interface).
3. Begin defining the MultiAlphaSort class:
class MultiAlphaSort implements iSort {
private $_order;
private $_index;
The MultiAlphaSort class implements iSort, per the Strategy design. The class has two private attributes, both of which will customize how the sort works.
I’ve chosen to name this MultiAlphaSort to indicate that it’s used to perform alphabetical sorts of multidimensional arrays. Another sort class might perform alphabetical sorts of one-dimensional arrays.
4. Define the constructor:
function _ _construct($index, $order = 'ascending') {
$this->_index = $index;
$this->_order = $order;
}
When a new object of this type is created, the sort order and index—the element in the array to be used as the basis of the sort—are assigned to the private attributes. As you’ll see in the code, this class can be used like so:
$obj = new MultiAlphaSort ('indexName', 'descending');
5. Begin defining the sort() method:
function sort(array $list) {
This method must have the same signature as that in the iSort interface. That just means a name of sort and a single argument.
6. Change the sorting algorithm based on the desired sort order:
if ($this->_order == 'ascending') {
uasort($list, array($this, 'ascSort'));
} else {
uasort($list, array($this, 'descSort'));
}
Within the method, the uasort() function is used to sort a multidimensional array (see Chapter 1). How that array is sorted will depend upon the chosen order, already stored in a private attribute.
The second argument to the uasort() function should be the name of the function to use for the comparison. Because that function—ascSort() or descSort()—is defined as a method in this current object, you can use this construct to reference it: array($this, 'methodName').
7. Complete the sort() method:
return $list;
}// End of sort() method.
The last step in the sort() method is to return the newly sorted array.
8. Define the comparison functions and complete the class:
function ascSort($x, $y) {
return strcasecmp ($x[$this->_index], $y[$this->_index]);
}
function descSort($x, $y) {
return strcasecmp ($y[$this->_index], $x[$this->_index]);
}
} // End of MultiAlphaSort class.
These functions, as explained in Chapter 1, take two arguments and return a value based upon the comparison of them. In this case, both $x and $y will be an array, and $this->_index represents what indexed value to sort upon.
9. Start defining the MultiNumberSort class:
class MultiNumberSort implements iSort {
private $_order;
private $_index;
function _ _construct($index, $order = 'ascending') {
$this->_index = $index;
$this->_order = $order;
}
This class is largely defined exactly like MultiAlphaSort.
10. Implement the sort() method:
function sort(array $list) {
if ($this->_order == 'ascending') {
uasort($list, array($this, 'ascSort'));
} else {
uasort($list, array($this, 'descSort'));
}
return $list;
}// End of sort() method.
This code is virtually the same as that in the other class.
11. Implement the comparison functions and complete the class:
function ascSort($x, $y) {
return ($x[$this->_index] > $y[$this->_index]);
}
function descSort($x, $y) {
return ($x[$this->_index] < $y[$this->_index]);
}
} // End of MultiNumberSort class.
12. Save the file as iSort.php and place it in your Web directory.
To use the iSort classes
1. Begin a new PHP script in your text editor or IDE, to be named strategy.php, starting with the HTML (Script 7.8):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Strategy</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 7.8 - strategy.php
Script 7.8. This script applies the iSort Strategy pattern to a multidimensional array.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Strategy</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 7.8 - strategy.php
10 // This page uses the iSort interface, plus the MultiAlphaSort and MultiNumberSort classes (Script 7.7).
11
12 // Load the class definition:
13 require('iSort.php');
14
15 /* The StudentsList class.
16 * The class contains one attribute: $_students.
17 * The class contains three methods:
18 * - _ _construct()
19 * - sort()
20 * - display()
21 */
22 class StudentsList {
23
24 // Stores the list of students:
25 private $_students = array();
26
27 // Constructors stores the list internally:
28 function _ _construct($list) {
29 $this->_students = $list;
30 }
31
32 // Perform a sort using an iSort implementation:
33 function sort(iSort $type) {
34 $this->_students = $type->sort($this->_students);
35 }
36
37 // Display the students as an HTML list:
38 function display() {
39 echo '<ol>';
40 foreach ($this->_students as $student) {
41 echo "<li>{$student['name']} {$student['grade']}</li>";
42 }
43 echo '</ol>';
44 }
45
46 } // End of StudentsList class.
47
48 // Create the array...
49 // Array structure:
50 // studentID => array('name' => 'Name', 'grade' => XX.X)
51 $students = array(
52 256 => array('name' => 'Jon', 'grade' => 98.5),
53 2 => array('name' => 'Vance', 'grade' => 85.1),
54 9 => array('name' => 'Stephen', 'grade' => 94.0),
55 364 => array('name' => 'Steve', 'grade' => 85.1),
56 68 => array('name' => 'Rob', 'grade' => 74.6)
57 );
58
59 // Create the main object:
60 $list = new StudentsList($students);
61
62 // Show the original array:
63 echo '<h2>Original Array</h2>';
64 $list->display();
65
66 // Sort by name:
67 $list->sort(new MultiAlphaSort('name'));
68 echo '<h2>Sorted by Name</h2>';
69 $list->display();
70
71 // Sort by grade:
72 $list->sort(new MultiNumberSort('grade', 'descending'));
73 echo '<h2>Sorted by Grade</h2>';
74 $list->display();
75
76 // Delete the object:
77 unset($list);
78 ?>
79 </body>
80 </html>
2. Load the class definition:
require('iSort.php');
3. Begin defining the StudentsList class:
class StudentsList {
private $_students = array();
function _ _construct($list) {
$this->_students = $list;
}
An object of the StudentsList type will actually use the sorting algorithms (this object plays the role of Context in ). This class’s constructor stores the multidimensional array internally.
4. Define the sort() method:
function sort(iSort $type) {
$this->_students = $type->sort($this->_students);
}
This sort() method will use the various algorithms (it doesn’t have to be named sort; it just makes sense to use that name here, too). This simple method is the heart of the Strategy pattern. On the fly, this method will receive an object of iSort type (you can do type hinting on interfaces, too). Within the method, that iSort object’s sort() method will be called, passing it the list of students. Because the implementations of sort() return the sorted list, the results of the method call have to be assigned to the StudentsList private attribute.
5. Create a display() method:
function display() {
echo '<ol>';
foreach ($this->_students as $student) {
echo "<li>{$student['name']} {$student['grade']}</li>";
}
echo '</ol>';
}
This method is just used to display the list of students as an ordered HTML list.
6. Complete the StudentsList class:
} // End of StudentsList class.
7. Create a multidimensional array:
$students = array(
256 => array('name' => 'Jon', 'grade' => 98.5),
2 => array('name' => 'Vance', 'grade' => 85.1),
9 => array('name' => 'Stephen', 'grade' => 94.0),
364 => array('name' => 'Steve', 'grade' => 85.1),
68 => array('name' => 'Rob', 'grade' => 74.6)
);
This code comes from Chapter 1.
8. Create the StudentsList object and display the original list:
$list = new StudentsList ($students);
echo '<h2>Original Array</h2>';
$list->display();
9. Sort the array alphabetically and redisplay:
$list->sort(new MultiAlphaSort ('name'));
echo '<h2>Sorted by Name</h2>';
$list->display();
To sort the list alphabetically by name, in ascending order, you need to pass the StudentList object’s sort() method an iSort class instance. That specific instance should also know that the index to be used in the sort is name. You could create the iSort instance using
$isort = new MultiAlphaSort ('name');
To save space, the creation of the object is done within the method call instead.
10. Sort the array numerically in descending order and redisplay:
$list->sort(new MultiNumberSort ('grade', 'descending'));
echo '<h2>Sorted by Grade</h2>';
$list->display();
Only the first line of code here is significantly different.
11. Complete the page:
unset($list);
?>
</body>
</html>
12. Save the file as strategy.php, place it in your Web directory, and test in your Web browser .
The list of students is sorted two different ways using algorithms implemented via Strategy.
Review and Pursue
If you have any problems with these sections, either in answering the questions or pursuing your own endeavors, turn to the book’s supporting forum (www.LarryUllman.com/forums/).
Review
• What is a design pattern? What are the four components of a design pattern definition? (See page 214.)
• What is the GoF? (See page 215.)
• What is the Singleton pattern? Under what circumstances is it useful? How does the Singleton pattern ensure a single instance of a class type? (See pages 216 and 217.)
• What is the Factory pattern? Under what circumstances is it useful? How does the Factory pattern generate new objects? (See page 220.)
• What is the Composite pattern? Under what circumstances is it useful? How do you design a Composite pattern? (See page 225.)
• What is the Strategy pattern? Under what circumstances is it useful? How do you design a Strategy pattern? (See page 233.)
Pursue
• Modify singleton.php so that the second Config object changes a setting. Then confirm the setting’s value via the first Config object.
• Create an HTML form that passes values to factory.php to create new shapes on the fly.
• Create a Circle class that extends Shape. Then update ShapeFactory to generate Circle objects, too.
• Update the Composite example to create a Department class. The Department class can be made up of teams and employees, and can also be assigned and complete tasks.
• Update the Composite example to create a Task class. Then modify the other classes to use type hinting for this object type.
• Create an implementation of iSort that sorts a single-dimensional array.
• Change the MultiNumberSort and MultiAlphaSort classes so that they inherit from a base class that defines the attributes and the constructor. Hint: The base class would implement the interface.
• After reading Chapter 8, add exception handling to this chapter’s examples.
• Learn more about design patterns using as many different sources as you can!
In This Chapter
Understanding Design Patterns
The Singleton Pattern
The Factory Pattern
The Composite Pattern
The Strategy Pattern
Review and Pursue
Once you begin regularly programming using objects, it’s not too long before you encounter the subject of design patterns. A design pattern is, simply put, a recommended best practice for solving a particular problem. In other words, if you’re trying to figure out how to implement such-and-such functionality, then use this design pattern as your approach.
This chapter introduces the concept of design patterns and walks you through four commonly used ones. Be forewarned that design patterns can be rather abstract, especially if the entire subject of OOP is still new to you.
Understanding Design Patterns
In the introduction, I state that a design pattern is a best practice for solving a particular problem. Before getting into some actual patterns, let’s look at what this definition means in more detail.
You may be surprised to learn that the first important book on design patterns was not a programming book at all, but one on architecture and city planning: A Pattern Language: Towns, Buildings, Construction by Christopher Alexander, Sara Ishikawa, and Murray Silverstein (Oxford University Press, 1977). This fact goes toward the first design pattern quality to grasp: design patterns are not specific bits of code that you can copy and paste as needed. No, design patterns are programming approaches, where the specific code—the implementation of that approach—may change from one situation to the next.
Second, a design pattern doesn’t just define a solution, but also the problem the solution is trying to address. One of the hardest things about learning design patterns is understanding which design pattern applies to a given situation.
But a design pattern is not made up of just a problem and a solution, but rather four key pieces:
• Its name
• A discussion of the problem: under what circumstances you would use the particular pattern
• The solution, which is not concrete in terms of code, but provides enough information for you to be able to reliably write the code
• The consequences: the pros and cons of the particular pattern
This last piece represents an important aspect of good software design. Identifying the consequences of an approach reflects the fact that while there may be best practices, there is no perfect solution for all possible situations. Understanding not just the problem and solution, but also the consequences will help you know when and if you should use a particular design pattern.
The rest of the chapter will introduce and demonstrate four design patterns. All of these were first defined in the “Gang of Four” book (see the sidebar). Before moving on, however, I want to add that design patterns, like OOP in general, are prone to being overly praised. Design patterns, like OOP, are a great tool to have in your developer toolbox. But neither design patterns nor OOP provides a magic bullet for all possible situations. As with any type of programming, the goal is to select the right tool for the job.
The Gang of Four
The seminal programming book on the subject of design patterns is Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley Professional, 1994). You’ll see this group of authors referred to as the “Gang of Four,” abbreviated as GoF.
The Design Patterns book identified 23 patterns, organized into three broad categories: creational, structural, and behavioral. The book primarily uses C++ for its examples, along with Smalltalk, but the whole point of design patterns is that they define approaches, regardless of the language in use.
Creational patterns create objects, saving you from having to do so manually in your code. The Builder, Factory, Prototype, and Singleton patterns are all creational; Factory and Singleton are covered in this chapter.
Structural patterns assist in the creation and use of complex structures. Examples of structural patterns include Adapter, Bridge, Composite (covered in this chapter), Decorator, Façade, and Proxy.
Behavioral patterns address how objects within a system communicate and how a program’s logic flows. The Command, Iterator, Observer, State, Strategy (covered in this chapter), and Template Method patterns are all behavioral.
As a second caveat, entire books have been written on design patterns. This one chapter is meant to be an introduction to the concept—providing what you need to get going. I’ve selected a range of patterns that will best demonstrate the variety of possible design patterns while sticking to those patterns that are easiest to grasp. I’ve also forgone the formality of covering each pattern’s consequences, as I feel doing so would have just added more words and things for you to think about, thereby making the learning that much harder.
The goal in this chapter is for you to get a handle on what design patterns are, how they are used, and what types of design patterns exist. Once you are comfortable with all that, try learning one new pattern at a time, using the Gang of Four book and online articles as resources. Take copious notes, and try not to move on to another pattern until you fully comprehend the one you are studying.
Tip
Some of these examples use additions to PHP’s OOP model as recent as PHP 5.3 and PHP 5.4. If you see any weird errors while running an example, it’s likely because you’re not using a current enough version of PHP.
Tip
As is the case with the Normal Forms in database design, you’ll see specific design patterns described in slightly different ways, often accenting one aspect of the problem or solution over another. Along with time and practice, it may take reading several different sources before you best comprehend the true heart of a particular pattern: problem and solution.
Tip
Being comfortable with design patterns makes project collaboration easier, as design patterns provide a vocabulary for common approaches.
The Singleton Pattern
The Singleton pattern is a creational pattern that will restrict an application to creating only a single instance of a particular class type. For example, a Web site will need a database connectivity object, but should have only one (you’d almost always want all database interactions going through a single connection), so you could use Singleton to enforce that restriction.
In terms of design, it’s relatively easy to implement the Singleton pattern . For starters, you can use a static attribute to guarantee that only one instance of a particular class exists.
The simple UML representation of the Singleton pattern. (See the previous three chapters for more on UML symbols.)
class SomeClass {
static private $_instance = NULL;
}
As explained in Chapter 5, “Advanced OOP,” because the attribute is static, it’s shared by all instances of the class. Taking that idea just one step further, if you store the actual instance in that attribute (i.e., assign an object to it), then all references to this class can use that attribute.
The next step is to create a method that will create an instance of the class if one does not exist and return the instance regardless (assuming the name of the class is SomeClass):
class SomeClass {
static private $_instance = NULL;
static function getInstance() {
if (self::$_instance == NULL) {
self::$_instance = new SomeClass();
}
return self::$_instance;
}
}
It’s common for a Singleton to name this method getInstance(). In the method, the conditional checks if the $_instance attribute still has a NULL value. If so, a new instance is created and assigned to the attribute. Finally, the instance is returned.
Now the class can be used in this manner:
$obj1 = SomeClass::getInstance();
If this is the first object in the application of that type, the instance will be created, assigned to the internal private attribute, and then returned.
Script 7.1. The Config class implements the Singleton pattern so that an entire Web application can make use of the same configuration object.
1 <?php # Script 7.1 - Config.php
2 // This page defines a Config class which uses the Singleton pattern.
3
4 /* The Config class.
5 * The class contains two attributes: $_instance and $settings.
6 * The class contains four methods:
7 * - _ _construct()
8 * - getInstance()
9 * - set()
10 * - get()
11 */
12 class Config {
13
14 // Store a single instance of this class:
15 static private $_instance = NULL;
16
17 // Store settings:
18 private $_settings = array();
19
20 // Private methods cannot be called:
21 private function _ _construct() {}
22 private function _ _clone() {}
23
24 // Method for returning the instance:
25 static function getInstance() {
26 if (self::$_instance == NULL) {
27 self::$_instance = new Config();
28 }
29 return self::$_instance;
30 }
31
32 // Method for defining a setting settings:
33 function set($index, $value) {
34 $this->_settings[$index] = $value;
35 }
36
37 // Method for retrieving a setting:
38 function get($index) {
39 return $this->_settings[$index];
40 }
41
42 } // End of Config class definition.
When a second object also calls that code, the same instance will be returned:
$obj2 = SomeClass::getInstance();
Now both $obj1 and $obj2 refer to the same instance of ClassName.
There is one catch, however. If a user tries to create a new object of that class type using new or clone, you would end up with multiple instances, defeating the purpose of a Singleton. The trick to preventing that from happening is to make a private constructor that does nothing:
private function _ _construct() {}
Now the following code will trigger an error :
$obj = new ClassName();
Because the class’s constructor is private, you cannot use new to create an object of this type.
Another good use for a Singleton is to create one global object, such as a configuration object used by an entire site. Let’s do that in the next series of steps.
To create a Singleton class
1. Begin a new PHP script in your text editor or IDE, to be named Config.php (Script 7.1):
<?php # Script 7.1 - Config.php
2. Start defining the Config class:
class Config {
Note that this is not an abstract class, as an instance of this class will be created.
3. Declare the two private attributes:
static private $_instance = NULL;
private $_settings = array();
The first attribute, $_instance, will represent a single instance of the class. The second attribute, $_settings, will store all the configuration settings. Both are initialized here.
4. Define a private constructor and a private _ _clone() method:
private function _ _construct() {}
private function _ _clone() {}
This code prevents the class from being instantiated using new or clone.
5. Define the getInstance() method:
static function getInstance() {
if (self::$_instance == NULL) {
self::$_instance = new Config();
}
return self::$_instance;
}
This method is defined using the code already explained.
6. Define the set() method:
function set($index, $value) {
$this->_settings[$index] = $value;
}
This method takes two arguments: a setting name, or index, and its value. The two arguments are used to manipulate the $_settings array.
7. Define the get() method:
function get($index) {
return $this->_settings[$index];
}
The get() method is used to return a current setting. After reading Chapter 8, “Using Existing Classes,” you’d likely want to have this method throw an exception should the function not be provided with an index that matches a previously stored value.
8. Complete the class:
} // End of Config class definition.
9. Save the file as Config.php and place it in your Web directory.
To use the Config class
1. Begin a new PHP script in your text editor or IDE, to be named singleton.php, starting with the HTML (Script 7.2):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Singleton</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Using a Singleton Config Object</h2>
<?php # Script 7.2 - singleton.php
2. Load the class definition:
require('Config.php');
Script 7.2. Thanks to the Singleton pattern, multiple variables will always reference the same object.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Singleton</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <h2>Using a Singleton Config Object</h2>
10 <?php # Script 7.2 - singleton.php
11 // This page uses the Config class (Script 7.1).
12
13 // Load the class definition:
14 require('Config.php');
15
16 // Create the object:
17 $CONFIG = Config::getInstance();
18
19 // Set some value:
20 $CONFIG->set('live', 'true');
21
22 // Confirm the current value:
23 echo '<p>$CONFIG["live"]: ' . $CONFIG->get('live') . '</p>';
24
25 // Create a second object to confirm:
26 $TEST = Config::getInstance();
27 echo '<p>$TEST["live"]: ' . $TEST->get('live') . '</p>';
28
29 // Delete the objects:
30 unset($CONFIG, $TEST);
31
32 ?>
33 </body>
34 </html>
3. Create a Config instance:
$CONFIG = Config::getInstance();
Remember that an instance is obtained through the static getInstance() method.
Frequently, an object which is a Singleton instance uses all capital letters (like a constant) for its name, although this is not required.
4. Establish a setting:
$CONFIG->set('live', 'true');
At this point, $CONFIG is an object of Config type, usable like any other object.
5. Print the setting’s value:
echo '<p>$CONFIG["live"]: ' . $CONFIG->get('live') . '</p>';
6. Create another configuration object and confirm the set value:
$TEST = Config::getInstance();
echo '<p>$TEST["live"]: ' . $TEST->get('live') . '</p>';
7. Complete the page:
unset($CONFIG, $TEST);
?>
</body>
</html>
8. Save the file as singleton.php, place it in your Web directory, and test in your Web browser .
Both variables refer to the same, lone class instance, thanks to the Singleton pattern.
Tip
In theory, code outside of any class could regulate the number of class instances. However, since you’re using OOP, and a single instance is a requirement of a class, it’d be best to design that functionality in the class.
The Factory Pattern
The Factory pattern is another creation pattern, like Singleton. But unlike Singleton, which creates and manages a single object of a single class type, the Factory pattern is used to manufacture potentially multiple objects of many different class types.
Of course, you already know how to create objects of a specific type:
$obj = SomeClass();
So why would you need a Factory to do that for you?
The Factory pattern becomes useful in situations where the type of object that needs to be generated isn’t known when the program is written but only once the program is running. In very dynamic applications, this can often be the case.
Another clue for when the Factory pattern might be appropriate is when there’s an abstract base class, and different derived subclasses will need to be created on the fly. This particular design structure is important with the Factory pattern, as once you’ve created the object, regardless of its specific type, the use of that object will be consistent.
The Factory pattern works via a static method, conventionally named Create(), factory(), factoryMethod(), or createInstance(). The method takes at least one argument, which indicates the type of object to create. The method then returns an object of that type :
static function Create($type) {
// Validate $type.
return new SomeClassType();
}
In the Factory pattern, the abstract Factory base class is extended by concrete Factory classes that will output objects of that class type (aka, products).
That’s the basic idea. Let’s see how this will be implemented in the following example, which will create a different type of Shape based on input provided to the page through the URL. This example will use the Shape, Rectangle, and Triangle classes from Chapter 6, “More Advanced OOP.” You will need to copy those to the same directory as the following files. Also, you can either also copy over the tDebug.php file, or remove that trait reference from the Rectangle class (see Chapter 6).
To create a Factory
1. Begin a new PHP script in your text editor or IDE, to be named ShapeFactory.php (Script 7.3):
<?php # Script 7.3 - ShapeFactory.php
2. Start defining the ShapeFactory class:
abstract class ShapeFactory {
The Factory class will be abstract, just as Shape itself is. You’ll never create an instance of the ShapeFactory class.
The class has no attributes.
3. Start defining the static method:
static function Create($type, array $sizes) {
This static method will take two arguments: the type of shape to create and an array of sizes. For a rectangle, that array would contain two values; for a triangle, three; and for a circle, only one (the radius).
Type hinting is used to enforce the requirement that $sizes be an array.
4. Create a different object based on the type value:
switch ($type) {
case 'rectangle':
return new Rectangle ($sizes[0], $sizes[1]);
break;
case 'triangle':
return new Triangle($sizes[0], $sizes[1], $sizes[2]);
break;
} // End of switch.
The switch checks the value of $type against the expected values. For now, just these two Shape types are recognized.
Script 7.3. The ShapeFactory class generates different Shape-derived objects on the fly.
1 <?php # Script 7.3 - ShapeFactory.php
2 // This page defines a ShapeFactory class which uses the Factory pattern.
3
4 /* The ShapeFactory class.
5 * The class contains no attributes.
6 * The class contains one method: Create().
7 */
8 abstract class ShapeFactory {
9
10 // Static method that creates objects:
11 static function Create($type, array $sizes) {
12
13 // Determine the object type based upon the parameters received.
14 switch ($type) {
15 case 'rectangle':
16 return new Rectangle($sizes[0], $sizes[1]);
17 break;
18 case 'triangle':
19 return new Triangle($sizes[0], $sizes[1], $sizes[2]);
20 break;
21 } // End of switch.
22
23 } // End of Create() method.
24
25 } // End of ShapeFactory class.
Again, a more complete version of this class would throw an exception if improper values were provided.
5. Complete the method and the class:
} // End of Create() method.
} // End of ShapeFactory class.
6. Save the file as ShapeFactory.php and place it in your Web directory.
To use the ShapeFactory class
1. Begin a new PHP script in your text editor or IDE, to be named factory.php, starting with the HTML (Script 7.4):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Factory</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 7.4 - factory.php
2. Load the class definitions:
require('ShapeFactory.php');
require('Shape.php');
require('Triangle.php');
require('Rectangle.php');
This script needs access to any possible Shape-derived class that might be used, as well as the Shape class definition itself, and ShapeFactory.
In the next chapter, you’ll learn how to establish an autoloader, so that the required class files can be included only when needed.
3. Perform some minimal validation:
if (isset($_GET['shape'], $_GET['dimensions'])) {
To make this example shorter, I’ve forgone the HTML form that the user might fill out to create different shapes and will just take values straight from the URL. The URL needs to include both a shape value and a dimensions value.
4. Create the object:
$obj = ShapeFactory::Create ($_GET['shape'], $_GET['dimensions']);
To create the object, the static method of the ShapeFactory class is invoked, passing it the values that came from the URL.
In a more realized version of this script, I’d add error handling here to confirm that the shape object was created before attempting to use it in the following steps.
5. Print an introduction:
echo "<h2>Creating a {$_GET['shape']}...</h2>";
6. Print the area:
echo '<p>The area is ' . $obj->getArea() . '</p>';
As $obj is now some sort of Shape-based object, you know it has a getArea() method that can be called.
7. Print the perimeter:
echo '<p>The perimeter is ' . $obj->getPerimeter() . '</p>';
Script 7.4. This script will create and use different types of shapes based on values passed in the URL.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Factory</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 7.4 - factory.php
10 // This page uses the ShapeFactory class (Script 7.2).
11
12 // Load the class definitions:
13 require('ShapeFactory.php');
14 require('Shape.php');
15 require('Triangle.php');
16 require('Rectangle.php');
17
18 // Minimal validation:
19 if (isset($_GET['shape'], $_GET['dimensions'])) {
20
21 // Create the new object:
22 $obj = ShapeFactory::Create($_GET['shape'], $_GET['dimensions']);
23
24 // Print a little introduction:
25 echo "<h2>Creating a {$_GET['shape']}...</h2>";
26
27 // Print the area:
28 echo '<p>The area is ' . $obj->getArea() . '</p>';
29
30 // Print the perimeter:
31 echo '<p>The perimeter is ' . $obj->getPerimeter() . '</p>';
32
33 } else {
34 echo '<p class="error">Please provide a shape type and size.</p>';
35 }
36
37 // Delete the object:
38 unset($obj);
39
40 ?>
41 </body>
42 </html>
8. Complete the minimal validation:
} else {
echo '<p class="error">Please provide a shape type and size.</p>';
}
9. Complete the page:
unset($obj);
?>
</body>
</html>
10. Save the file as factory.php, place it in your Web directory, along with the other class files, and test in your Web browser .
Based on values passed in the URL, this script reports the area and perimeter of a rectangle.
You’ll need to use a URL like factory.php?shape=rectangle& dimensions[]=10&dimensions[]=14.
11. Provide new parameters in the URL and retest in your Web browser .
Just changing the URL parameters, without touching the underlying code, now reports on a triangle.
Tip
The Factory pattern is also meant to be easily extendible (i.e., supporting new classes as they are defined).
Tip
One of the consequences of using the Factory pattern is that the Factory class is very tightly coupled with the rest of the application, due to its internal validation. You can see this in the ShapeFactory Create() method, which makes assumptions about the incoming $sizes array.
Tip
A variation on the Factory pattern is Abstract Factory. Whereas the Factory pattern outputs different objects, all derived from the same parent, the Abstract Factory outputs other factories.
The Composite Pattern
The Singleton and Factory patterns are creational: used to generate one or more objects. Another category of patterns is structural. These patterns apply in situations where a nontraditional class structure, or a modification of an existing class structure, is required. For an example of a structural pattern, I want to introduce Composite.
The focus in Chapter 5 is on inheritance as a way of basing one class definition on another. In Chapter 6, I talk about composition, which is another design approach. The composition explained there involves one class type (e.g., Department) being composed of other class types (e.g.,Employees), which will commonly happen. Similarly, the Composite pattern applies in situations where you have an object that might represent a single entity or a composite entity, but still needs to be usable in the same manner.
For example, an HTML form contains one or more form elements. From a programming perspective, certain behaviors will apply to both the entire form and its individual components:
• Display
• Validate
• Show errors
Addressing these needs without using patterns would require that you replicate a lot of code in two different classes (i.e., Form and FormElement). And, as you should know by now, replication makes for bad programming. The solution to this problem is to apply Composite so that the entire form or just an individual component can be treated the same. For example, you could show errors on the form or on a single element; you could validate the entire form at once or just a single element (e.g., validate the availability of a username via Ajax).
Another sign that the Composite pattern may apply is when you’re working with a tree-like structure. For example, the whole form is the root of the tree, and the different form elements would be the branches (often called “leaves” in Composite jargon).
To implement the Composite pattern, you’ll normally start with an abstract base class that will be extended by the different subclasses. The base class needs to identify the methods for adding and removing “leaves” (or composite items). The base class also needs to identify any functionality that the composite, or its subelements, needs to do:
abstract class FormComponent {
abstract function add (FormComponent $obj);
abstract function remove (FormComponent $obj);
abstract function display();
abstract function validate();
abstract function showError();
}
As you can see, the abstract class here is also using type hinting (see Chapter 6). The first two methods are key to Composite. The other three represent the specific functionality needed by this particular example.
Each subclass now inherits from this derived class. By definition, each must also define the implementation of the abstract methods. Here’s a partial example:
class Form extends FormComponent {
private $_elements = array();
function add(FormComponent $obj) {
$this->_elements[] = $obj;
}
function display() {
// Display the entire form.
}
}
class FormElement extends FormComponent {
function add(FormComponent $obj) {
return $obj; // Or false.
}
function display() {
// Display the element.
}
}
As you can see in that code, the Form class implements a true add() method, which allows you to add elements to a form:
$form = new Form();
$email = new FormElement();
$form->add($email);
Note that the FormElement class still has to define the add() method, but the method shouldn’t do anything, because you don’t add subelements to that class type. Instead, such add methods will conventionally return the object that’s being added, or false, or throw an exception.
With these class definitions, you can now treat entire forms or individual form elements in the same manner.
To demonstrate another implementation of the Composite pattern, let’s create a variation on the Department-Employee example. Here, there will be two types of work units: employees and teams, with the latter being made up of employees. Jobs to be done can be assigned to, and completed by, either.
To create a Composite design
1. Begin a new PHP script in your text editor or IDE, to be named WorkUnit.php (Script 7.5):
<?php # Script 7.5 - WorkUnit.php
2. Start defining the WorkUnit class:
abstract class WorkUnit {
Again, this is an abstract class, because you won’t create objects of this class type.
3. Declare two protected attributes:
protected $tasks = array();
protected $name = NULL;
These two attributes will be used by any class that extends this one. The first attribute stores the jobs to be done. The second stores the name of the current object, be it a team or an employee.
4. Define the constructor:
function _ _construct($name) {
$this->name = $name;
}
The constructor is not abstract; it’s fully implemented and assigns the name of the team or employee to the protected attribute.
Script 7.5. The WorkUnit abstract class is extended by two concrete classes to implement the Composite pattern.
1 <?php # Script 7.5 - WorkUnit.php
2 // This page defines a WorkUnit class which uses the Composite pattern.
3 // This page also defines Team and Employee classes, which extend WorkUnit.
4
5 /* The WorkUnit class.
6 * The class contains two attributes: $tasks and $name.
7 * The class contains five methods: _ _construct(), getName(), add(), remove(), assignTask(), and completeTask().
8 */
9 abstract class WorkUnit {
10
11 // For storing work to be done:
12 protected $tasks = array();
13
14 // For storing the employee or team name:
15 protected $name = NULL;
16
17 // Constructor assigns the name:
18 function _ _construct($name) {
19 $this->name = $name;
20 }
21
22 // Method that returns the name:
23 function getName() {
24 return $this->name;
25 }
26
27 // Abstract functions to be implemented:
28 abstract function add(Employee $e);
29 abstract function remove(Employee $e);
30 abstract function assignTask($task);
31 abstract function completeTask($task);
32
33 } // End of WorkUnit class.
34
35 /* The Team class extends WorkUnit.
36 * The class has one new attribute: $_employees.
37 * The class has one new method: getCount().
38 */
39 class Team extends WorkUnit {
40
41 // For storing team members:
42 private $_employees = array();
43
44 // Implement the abstract methods...
45 function add(Employee $e) {
46 $this->_employees[] = $e;
47 echo "<p>{$e->getName()} has been added to team {$this->getName()}.</p>";
48 }
49 function remove(Employee $e) {
50 $index = array_search($e, $this->_employees);
51 unset($this->_employees[$index]);
52 echo "<p>{$e->getName()} has been removed from team {$this->getName()}.</p>";
53 }
54 function assignTask($task) {
55 $this->tasks[] = $task;
56 echo "<p>A new task has been assigned to team {$this->getName()}. It should be easy to do with {$this->getCount()} team member(s).</p>";
57 }
58 function completeTask($task) {
59 $index = array_search($task, $this->tasks);
60 unset($this->tasks[$index]);
61 echo "<p>The '$task' task has been completed by team {$this->getName()}.</p>";
62 }
63
64 // Method for returning the number of team members:
65 function getCount() {
66 return count($this->_employees);
67 }
68
69 } // End of Team class.
70
71 /* The Employee class extends WorkUnit.
72 * The class has no new attributes or methods.
73 */
74 class Employee extends WorkUnit {
75
76 // Empty functions:
77 function add(Employee $e) {
78 return false;
79 }
80 function remove(Employee $e) {
81 return false;
82 }
83
84 // Implement the abstract methods...
85 function assignTask($task) {
86 $this->tasks[] = $task;
87 echo "<p>A new task has been assigned to {$this->getName()}. It will be done by {$this->getName()} alone.</p>";
88 }
89 function completeTask($task) {
90 $index = array_search($task, $this->tasks);
91 unset($this->tasks[$index]);
92 echo "<p>The '$task' task has been completed by employee {$this->getName()}.</p>";
93 }
94
95 } // End of Employee class.
5. Define the getName() method:
function getName() {
return $this->name;
}
This method is just a “getter” for the $name attribute. I created this method only for better output in the final script (i.e., to be able to see which employee or team is doing what), but it is not inherent to the Composite pattern approach.
6. Identify the abstract methods:
abstract function add (Employee $e);
abstract function remove (Employee $e);
abstract function assignTask ($task);
abstract function completeTask ($task);
For this example, I’m defining four abstract methods. Two are required by Composite: one for adding composite pieces (i.e., employees) and one for removing them. The other two methods are unique to this example and demonstrate the functionality that both specific classes can do.
7. Complete the class:
} // End of WorkUnit class.
8. Start defining the Team class:
class Team extends WorkUnit {
private $_employees = array();
The Team class extends WorkUnit, meaning that it inherits the constructor and getName() methods, as well as the two protected attributes. It adds a new attribute, which will store the “leaves.”
9. Implement the add() method:
function add(Employee $e) {
$this->_employees[] = $e;
echo "<p>{$e->getName()} has been added to team {$this->getName()}.</p>";
}
This method will be used to add composite members to the class. It uses type hinting to expect an Employee object, which is then added to the internal array. So that the script has something to show, a message reports the name of the employee added to the team.
10. Implement the remove() method:
function remove(Employee $e) {
$index = array_search($e, $this->_employees);
unset($this->_employees[$index]);
echo "<p>{$e->getName()} has been removed from team {$this->getName()}.</p>";
}
This method is for removing team members from the composite object. To do so, the index must first be found for the employee being removed. Then this element is unset from the array. In a more fleshed-out class, I’d check that $index has a positive value first.
Again, a message indicates what’s happening.
11. Define the assignTask() method:
function assignTask($task) {
$this->tasks[] = $task;
echo "<p>A new task has been assigned to team {$this->getName()}. It should be easy to do with {$this->getCount()} team member(s).</p>";
}
Here, a new job is added to the ongoing list of responsibilities for the team. A message indicates that the task has been added, and shows how many employees (i.e., members of this team) will be available to work on the task.
12. Define the completeTask() method:
function completeTask($task) {
$index = array_search($task, $this->tasks);
unset($this->tasks[$index]);
echo "<p>The '$task' task has been completed by team {$this->getName()}.</p>";
}
This method is similar to remove(), this time removing completed tasks.
13. Define the getCount() method and complete the class:
function getCount() {
return count ($this->_employees);
}
} // End of Team class.
This method simply returns the number of employees in the team.
14. Begin defining the Employee class:
class Employee extends WorkUnit {
function add(Employee $e) {
return false;
}
function remove(Employee $e) {
return false;
}
The Employee class also extends WorkUnit, so it must define the add() and remove() methods, even if both just return false.
15. Implement the two remaining abstract methods:
function assignTask($task) {
$this->tasks[] = $task;
echo "<p>A new task has been assigned to {$this->getName()}. It will be done by {$this->getName()} alone.</p>";
}
function completeTask($task) {
$index = array_search($task, $this->tasks);
unset($this->tasks[$index]);
echo "<p>The '$task' task has been completed by employee {$this->getName()}.</p>";
}
These two methods work similarly to those in Team, with a couple of employee-specific changes.
16. Complete the class:
} // End of Employee class.
17. Save the file as WorkUnit.php and place it in your Web directory.
Script 7.6. In this script, the Composite pattern is implemented in different types of work units.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Composite</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <h2>Using Composite</h2>
10 <?php # Script 7.6 - composite.php
11 // This page uses the WorkUnit, Team, and Employee classes (Script 7.5).
12
13 // Load the class definition:
14 require('WorkUnit.php');
15
16 // Create the objects:
17 $alpha = new Team('Alpha');
18 $john = new Employee('John');
19 $cynthia = new Employee('Cynthia');
20 $rashid = new Employee('Rashid');
21
22 // Assign employees to the team:
23 $alpha->add($john);
24 $alpha->add($rashid);
25
26 // Assign tasks:
27 $alpha->assignTask('Do something great.');
28 $cynthia->assignTask('Do something grand.');
29
30 // Complete a task:
31 $alpha->completeTask('Do something great.');
32
33 // Remove a team member:
34 $alpha->remove($john);
35
36 // Delete the objects:
37 unset($alpha, $john, $cynthia, $rashid);
38 ?>
39 </body>
40 </html>
To use the Composite classes
1. Begin a new PHP script in your text editor or IDE, to be named composite.php, starting with the HTML (Script 7.6):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Composite</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h2>Using Composite</h2>
<?php # Script 7.6 - composite.php
2. Load the class definition:
require('WorkUnit.php');
3. Create the objects:
$alpha = new Team('Alpha');
$john = new Employee('John');
$cynthia = new Employee ('Cynthia');
$rashid = new Employee('Rashid');
Here are four different objects: one Team and three Employees.
4. Assign employees to the team:
$alpha->add($john);
$alpha->add($rashid);
5. Assign tasks:
$alpha->assignTask('Do something great.');
$cynthia->assignTask('Do something grand.');
Because WorkUnit uses the Composite pattern, all four objects can be treated the same from this point forward.
6. Complete a task and remove a team member:
$alpha->completeTask('Do something great.');
$alpha->remove($john);
This is just a test of the functionality of the class.
7. Complete the page:
unset($alpha, $john, $cynthia, $rashid);
?>
</body>
</html>
8. Save the file as composite.php, place it in your Web directory, and test in your Web browser .
The Composite pattern allows one class type to be used like another, even if one is composed of the other class type.
Tip
The Visitor pattern, which allows for operations across structures, is often used in conjunction with Composite.
Tip
This example could be extended so that teams could be made up of employees or other teams.
Tip
Another way of implementing this example using the Composite pattern would be to focus on the tasks. New tasks could be treated individually, or created as steps in other tasks. Then an individual task or a group of tasks could be assigned to work units.
Antipatterns
In learning about design patterns, you’ll often come across the term antipattern. A design pattern is a best practice; an antipattern is, therefore, an example of what we know did not work. As any developer comes to know, you learn as much, if not more, from your failures as from your successes.
The Strategy Pattern
The final pattern example to be explained in this chapter, Strategy, is a type of behavioral pattern. Behavioral patterns are used to address how an application runs. As a comparison, the Factory pattern can change the object type on the fly, but the Strategy pattern can change an algorithm on the fly (an algorithm just being a process or set of code used to perform a calculation or solve a problem). Strategy is most useful in situations where you have classes that may be similar, but not related, and differ only in their specific behavior.
For example, say you need a filtering system for strings. Different filters might include:
• Stripping out HTML
• Crossing out swear words
• Catching character combinations that can be used to send spam through contact forms and the like
The only quality that these three approaches have in common is that they’re all applied to strings. Other than that, there are no commonalities that might suggest they’d make sense as derived subclasses from a common base class.
Furthermore, these filters might be applied differently on the fly. For example, an application switch might indicate whether or not HTML or swear words are allowed in text. This, then, is a good candidate for the Strategy pattern.
First, start by defining an interface that dictates the needed functionality:
interface Filter {
function filter($str);
}
(For a refresher on interfaces, see Chapter 6.)
Specific filter types then implement their specific versions of the interface’s method:
class HtmlFilter implements Filter {
function filter($str) {
// Strip out the HTML.
return $str;
}
}
class SwearFilter implements Filter {
function filter($str) {
// Cross out swear words.
return $str;
}
}
Finally, another class would be written that could use any filter :
class FormData {
private $_data = NULL;
function _ _construct($input) {
$this->_data = $input;
}
function process(Filter $type) {
$this->_data = $type->filter($this->_data);
}
}
In the Strategy pattern, an interface is extended by more concrete classes, which in turn are used by a specific object (the Context).
In that code, the process() method expects to receive an object of type Filter through which the data would then be run.
And here’s how you would use this:
$form = new FormData($someUserInput);
if (/* No HTML allowed. */) {
$form->process(new HtmlFilter());
}
if (/* No swear words allowed. */) {
$form->process(new SwearFilter());
}
As an alternative use of Strategy, let’s take the multidimensional sort example from Chapter 1, “Advanced PHP Techniques,” and implement it using objects. To make the algorithms as reusable as possible, each algorithm will take constructor arguments to more finely tune its behavior.
Script 7.7. The iSort interface is implemented by two classes in order to create different sorting algorithms.
1 <?php # Script 7.7 - iSort.php
2 // This page defines an iSort interface and two classes.
3 // This page implements the Strategy pattern.
4
5 // The Sort interface defines the sort() method:
6 interface iSort {
7 function sort(array $list);
8 }
9
10 // The MultiAlphaSort sorts a multidimensional array alphabetically.
11 class MultiAlphaSort implements iSort {
12
13 // How to sort:
14 private $_order;
15
16 // Sort index:
17 private $_index;
18
19 // Constructor sets the sort index and order:
20 function _ _construct($index, $order = 'ascending') {
21 $this->_index = $index;
22 $this->_order = $order;
23 }
24
25 // Function does the actual sorting:
26 function sort(array $list) {
27
28 // Change the algorithm to match the sort preference:
29 if ($this->_order == 'ascending') {
30 uasort($list, array($this, 'ascSort'));
31 } else {
32 uasort($list, array($this, 'descSort'));
33 }
34
35 // Return the sorted list:
36 return $list;
37
38 }// End of sort() method.
39
40 // Functions that compares two values:
41 function ascSort($x, $y) {
42 return strcasecmp($x[$this->_index], $y[$this->_index]);
43 }
44 function descSort($x, $y) {
45 return strcasecmp($y[$this->_index], $x[$this->_index]);
46 }
47
48 } // End of MultiAlphaSort class.
49
50 // The MultiNumberSort sorts a multidimensional array numerically.
51 class MultiNumberSort implements iSort {
52
53 // How to sort:
54 private $_order;
55
56 // Sort index:
57 private $_index;
58
59 // Constructor sets the sort index and order:
60 function _ _construct($index, $order = 'ascending') {
61 $this->_index = $index;
62 $this->_order = $order;
63 }
64
65 // Function does the actual sorting:
66 function sort(array $list) {
67
68 // Change the algorithm to match the sort preference:
69 if ($this->_order == 'ascending') {
70 uasort($list, array($this, 'ascSort'));
71 } else {
72 uasort($list, array($this, 'descSort'));
73 }
74
75 // Return the sorted list:
76 return $list;
77
78 }// End of sort() method.
79
80 // Functions that compares two values:
81 function ascSort($x, $y) {
82 return ($x[$this->_index] > $y[$this->_index]);
83 }
84 function descSort($x, $y) {
85 return ($x[$this->_index] < $y[$this->_index]);
86 }
87
88 } // End of MultiNumberSort class.
To create a Strategy design
1. Begin a new PHP script in your text editor or IDE, to be named iSort.php (Script 7.7):
<?php # Script 7.7 - iSort.php
2. Define the iSort interface:
interface iSort {
function sort(array $list);
}
This interface is very simple, requiring only a single method: sort(). That method takes an array as its lone argument and will return the sorted version of the array (although the return value is not indicated in the interface).
3. Begin defining the MultiAlphaSort class:
class MultiAlphaSort implements iSort {
private $_order;
private $_index;
The MultiAlphaSort class implements iSort, per the Strategy design. The class has two private attributes, both of which will customize how the sort works.
I’ve chosen to name this MultiAlphaSort to indicate that it’s used to perform alphabetical sorts of multidimensional arrays. Another sort class might perform alphabetical sorts of one-dimensional arrays.
4. Define the constructor:
function _ _construct($index, $order = 'ascending') {
$this->_index = $index;
$this->_order = $order;
}
When a new object of this type is created, the sort order and index—the element in the array to be used as the basis of the sort—are assigned to the private attributes. As you’ll see in the code, this class can be used like so:
$obj = new MultiAlphaSort ('indexName', 'descending');
5. Begin defining the sort() method:
function sort(array $list) {
This method must have the same signature as that in the iSort interface. That just means a name of sort and a single argument.
6. Change the sorting algorithm based on the desired sort order:
if ($this->_order == 'ascending') {
uasort($list, array($this, 'ascSort'));
} else {
uasort($list, array($this, 'descSort'));
}
Within the method, the uasort() function is used to sort a multidimensional array (see Chapter 1). How that array is sorted will depend upon the chosen order, already stored in a private attribute.
The second argument to the uasort() function should be the name of the function to use for the comparison. Because that function—ascSort() or descSort()—is defined as a method in this current object, you can use this construct to reference it: array($this, 'methodName').
7. Complete the sort() method:
return $list;
}// End of sort() method.
The last step in the sort() method is to return the newly sorted array.
8. Define the comparison functions and complete the class:
function ascSort($x, $y) {
return strcasecmp ($x[$this->_index], $y[$this->_index]);
}
function descSort($x, $y) {
return strcasecmp ($y[$this->_index], $x[$this->_index]);
}
} // End of MultiAlphaSort class.
These functions, as explained in Chapter 1, take two arguments and return a value based upon the comparison of them. In this case, both $x and $y will be an array, and $this->_index represents what indexed value to sort upon.
9. Start defining the MultiNumberSort class:
class MultiNumberSort implements iSort {
private $_order;
private $_index;
function _ _construct($index, $order = 'ascending') {
$this->_index = $index;
$this->_order = $order;
}
This class is largely defined exactly like MultiAlphaSort.
10. Implement the sort() method:
function sort(array $list) {
if ($this->_order == 'ascending') {
uasort($list, array($this, 'ascSort'));
} else {
uasort($list, array($this, 'descSort'));
}
return $list;
}// End of sort() method.
This code is virtually the same as that in the other class.
11. Implement the comparison functions and complete the class:
function ascSort($x, $y) {
return ($x[$this->_index] > $y[$this->_index]);
}
function descSort($x, $y) {
return ($x[$this->_index] < $y[$this->_index]);
}
} // End of MultiNumberSort class.
12. Save the file as iSort.php and place it in your Web directory.
To use the iSort classes
1. Begin a new PHP script in your text editor or IDE, to be named strategy.php, starting with the HTML (Script 7.8):
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Strategy</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 7.8 - strategy.php
Script 7.8. This script applies the iSort Strategy pattern to a multidimensional array.
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Strategy</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 7.8 - strategy.php
10 // This page uses the iSort interface, plus the MultiAlphaSort and MultiNumberSort classes (Script 7.7).
11
12 // Load the class definition:
13 require('iSort.php');
14
15 /* The StudentsList class.
16 * The class contains one attribute: $_students.
17 * The class contains three methods:
18 * - _ _construct()
19 * - sort()
20 * - display()
21 */
22 class StudentsList {
23
24 // Stores the list of students:
25 private $_students = array();
26
27 // Constructors stores the list internally:
28 function _ _construct($list) {
29 $this->_students = $list;
30 }
31
32 // Perform a sort using an iSort implementation:
33 function sort(iSort $type) {
34 $this->_students = $type->sort($this->_students);
35 }
36
37 // Display the students as an HTML list:
38 function display() {
39 echo '<ol>';
40 foreach ($this->_students as $student) {
41 echo "<li>{$student['name']} {$student['grade']}</li>";
42 }
43 echo '</ol>';
44 }
45
46 } // End of StudentsList class.
47
48 // Create the array...
49 // Array structure:
50 // studentID => array('name' => 'Name', 'grade' => XX.X)
51 $students = array(
52 256 => array('name' => 'Jon', 'grade' => 98.5),
53 2 => array('name' => 'Vance', 'grade' => 85.1),
54 9 => array('name' => 'Stephen', 'grade' => 94.0),
55 364 => array('name' => 'Steve', 'grade' => 85.1),
56 68 => array('name' => 'Rob', 'grade' => 74.6)
57 );
58
59 // Create the main object:
60 $list = new StudentsList($students);
61
62 // Show the original array:
63 echo '<h2>Original Array</h2>';
64 $list->display();
65
66 // Sort by name:
67 $list->sort(new MultiAlphaSort('name'));
68 echo '<h2>Sorted by Name</h2>';
69 $list->display();
70
71 // Sort by grade:
72 $list->sort(new MultiNumberSort('grade', 'descending'));
73 echo '<h2>Sorted by Grade</h2>';
74 $list->display();
75
76 // Delete the object:
77 unset($list);
78 ?>
79 </body>
80 </html>
2. Load the class definition:
require('iSort.php');
3. Begin defining the StudentsList class:
class StudentsList {
private $_students = array();
function _ _construct($list) {
$this->_students = $list;
}
An object of the StudentsList type will actually use the sorting algorithms (this object plays the role of Context in ). This class’s constructor stores the multidimensional array internally.
4. Define the sort() method:
function sort(iSort $type) {
$this->_students = $type->sort($this->_students);
}
This sort() method will use the various algorithms (it doesn’t have to be named sort; it just makes sense to use that name here, too). This simple method is the heart of the Strategy pattern. On the fly, this method will receive an object of iSort type (you can do type hinting on interfaces, too). Within the method, that iSort object’s sort() method will be called, passing it the list of students. Because the implementations of sort() return the sorted list, the results of the method call have to be assigned to the StudentsList private attribute.
5. Create a display() method:
function display() {
echo '<ol>';
foreach ($this->_students as $student) {
echo "<li>{$student['name']} {$student['grade']}</li>";
}
echo '</ol>';
}
This method is just used to display the list of students as an ordered HTML list.
6. Complete the StudentsList class:
} // End of StudentsList class.
7. Create a multidimensional array:
$students = array(
256 => array('name' => 'Jon', 'grade' => 98.5),
2 => array('name' => 'Vance', 'grade' => 85.1),
9 => array('name' => 'Stephen', 'grade' => 94.0),
364 => array('name' => 'Steve', 'grade' => 85.1),
68 => array('name' => 'Rob', 'grade' => 74.6)
);
This code comes from Chapter 1.
8. Create the StudentsList object and display the original list:
$list = new StudentsList ($students);
echo '<h2>Original Array</h2>';
$list->display();
9. Sort the array alphabetically and redisplay:
$list->sort(new MultiAlphaSort ('name'));
echo '<h2>Sorted by Name</h2>';
$list->display();
To sort the list alphabetically by name, in ascending order, you need to pass the StudentList object’s sort() method an iSort class instance. That specific instance should also know that the index to be used in the sort is name. You could create the iSort instance using
$isort = new MultiAlphaSort ('name');
To save space, the creation of the object is done within the method call instead.
10. Sort the array numerically in descending order and redisplay:
$list->sort(new MultiNumberSort ('grade', 'descending'));
echo '<h2>Sorted by Grade</h2>';
$list->display();
Only the first line of code here is significantly different.
11. Complete the page:
unset($list);
?>
</body>
</html>
12. Save the file as strategy.php, place it in your Web directory, and test in your Web browser .
The list of students is sorted two different ways using algorithms implemented via Strategy.
Review and Pursue
If you have any problems with these sections, either in answering the questions or pursuing your own endeavors, turn to the book’s supporting forum (www.LarryUllman.com/forums/).
Review
• What is a design pattern? What are the four components of a design pattern definition? (See page 214.)
• What is the GoF? (See page 215.)
• What is the Singleton pattern? Under what circumstances is it useful? How does the Singleton pattern ensure a single instance of a class type? (See pages 216 and 217.)
• What is the Factory pattern? Under what circumstances is it useful? How does the Factory pattern generate new objects? (See page 220.)
• What is the Composite pattern? Under what circumstances is it useful? How do you design a Composite pattern? (See page 225.)
• What is the Strategy pattern? Under what circumstances is it useful? How do you design a Strategy pattern? (See page 233.)
Pursue
• Modify singleton.php so that the second Config object changes a setting. Then confirm the setting’s value via the first Config object.
• Create an HTML form that passes values to factory.php to create new shapes on the fly.
• Create a Circle class that extends Shape. Then update ShapeFactory to generate Circle objects, too.
• Update the Composite example to create a Department class. The Department class can be made up of teams and employees, and can also be assigned and complete tasks.
• Update the Composite example to create a Task class. Then modify the other classes to use type hinting for this object type.
• Create an implementation of iSort that sorts a single-dimensional array.
• Change the MultiNumberSort and MultiAlphaSort classes so that they inherit from a base class that defines the attributes and the constructor. Hint: The base class would implement the interface.
• After reading Chapter 8, add exception handling to this chapter’s examples.
• Learn more about design patterns using as many different sources as you can!