Victorious warriors win first and then go to war, while defeated warriors go to war first and then seek to win.
In preparing for battle I have always found that plans are useless, but planning is indispensable.
All I had done was to improve on their strategy, and it was the beginning of a very important lesson in life—that anytime you find someone more successful than you are, especially when you’re both engaged in the same business—you know they’re doing something that you aren’t.
One of the ongoing tasks in using PHP with MySQL is writing algorithms for the different kinds of requests made of a MySQL application. Typical requests include creating tables or entering, selecting, changing, and deleting data. The algorithms for these different requests are simple or complex depending on both the complexity of the request and of the table.
One of the main principles of design patterns is to encapsulate what varies. With several different algorithms for different kinds of requests sent to a PHP class that handles these MySQL requests, the variation is clearly the algorithms. The variations may be small or great, but using the Strategy design pattern, we can greatly simplify the process.
Generally, with design patterns, the question is “What causes redesign?” Then, we proceed to avoid those things that force redesign. But what if we instead think of a way to make changes without redesign? By encapsulating what varies, programmers first decide what will vary in a program and then encapsulate those features. When a design requires change, the encapsulated elements can be changed without affecting the rest of the system. Since different MySQL tasks require different algorithms, the algorithms (tasks) can be encapsulated, and the Strategy design pattern applied.
To get started, take a look at Figure 12-1. It shows the class diagram for the Strategy design pattern.
Now go back to Chapter 10 and look at Figure 10-1. The pattern participants are organized in a very similar fashion to what you see in Figure 12-1. The Context participant has an aggregate relationship with an interface in both designs. With the State pattern it is the State interface, and with the Strategy pattern it is the Strategy interface. Otherwise they appear identical.
To understand the differences, you need to see how the different Context participants behave in relationship to the Strategy and State interfaces and their concrete implementations. Table 12-1 summarizes these differences.
Table 12-1. Context and variation differences between State and Strategy design patterns
Pattern | What Varies? | Context |
---|---|---|
State | State | Maintains an instance of the current state of the subclass that defines the current state. |
Strategy | Algorithm | Configured with concrete strategy object—an encapsulated algorithm. |
As you saw in Chapter 10, the Context kept a variable holding the current concrete state. The concrete states provide methods for going to another state from the current state as recorded in a Context variable.
However, the Context participant in the Strategy pattern has no ongoing record of the current strategy in use. It has no reason to because unlike changing states, generally changing algorithms is not dependent on the current one in use. Obviously, you have situations where one algorithm should be used before another, such as inserting data into a table before attempting to retrieve it. Nevertheless, that does not prevent the algorithm from being used to attempt to retrieve data from an empty table. In the State pattern, though, it is easy to have a state that can go to only certain states and not others. In the three-way light example in Chapter 10, a light state in the second on state cannot go to either the first on state or the off state. It can go only to the third on state. (See Figure 10-5 in Chapter 10.) Such is not the case with most algorithms.
One of the many features of both the State and Strategy design patterns is that the Context participants avoid conditional statements. If you look at the examples in Chapter 10, you’ll notice that none have conditional statements. In the When to Use the State Pattern? section in Chapter 10, you will find pseudocode that illustrates the difficulty in moving from one cell to the next using conditional statements. (Never mind what it would take when new states are added to or changed in an existing set of states!)
Design patterns certainly do not advocate never using conditional
or case statements, but in some patterns, such as the State and Strategy,
they can make maintenance difficult. If a single strategy (encapsulated
algorithm) is changed and it requires changing a whole set of
conditional or case statements, there’s a greater chance that errors can
be introduced. Further, in using either pattern, introducing conditional
or case statements in the client participant is acceptable because all
that the client does is make requests. Further, in the encapsulated
algorithms (concrete strategies), carrying out a task may require a
conditional or case statement. Likewise, in data output and error
checking using mysqli
, a conditional
statement is often essential. Strategies eliminate conditional
statements for selecting desired behavior. The different tasks are
handled by the different concrete strategies, and because the client
requests concrete strategies through the context, it must be aware of
the available strategies. This does not mean that conditionals cannot be
part of the client’s selection process. It means that conditionals are
not part of the context.
One of the less detailed elements in GoF’s Design Patterns is the notion of a family of algorithms. Developers are left to define a family of algorithms, but GoF does not specify the exact meaning of “family” in the context of design patterns. However, in the book Head First Design Patterns, Eric and Elizabeth Freeman provide a simple yet useful concept—a set of behaviors. Any project that relies on a certain set of behaviors can be cast into a Strategy design pattern by encapsulating the behaviors into strategies. That is, the behaviors require some kind of algorithm to make them functional. By encapsulating them into concrete strategies, they can be used, reused, and modified.
This chapter’s “family” consists of the behaviors typically
required in work with a MySQL table. Data are entered, changed,
retrieved, and deleted. Those behaviors involving data operations then
become the “family” that can be cast into a strategy for each family
member. The strategies require slightly different algorithms in
conjunction with the MySQL commands and the PHP mysqli
class. By putting these operations into
separate concrete classes that implement a common interface, they can be
incorporated as part of a Strategy design pattern.
In order to see the general pattern using a MySQL connection, this first example uses no table but instead sets up the pattern for later incorporation of the details in each strategy. Because the HTML form cannot pass selected parameters to a PHP class or file, this example uses several small PHP trigger scripts. The trigger scripts call different methods from a single client that in turn calls the requested concrete strategy through the context.
Figure 12-2 shows the file
diagram for this implementation. The HTML file has separate forms for each
strategy that is passed through a PHP trigger script to methods in the
Client
. The Client
passes on the
request through the Context
to a concrete strategy. The
connection helpers are an interface and class to connect to a MySQL
database.
For those developers who have considerable experience dealing with MySQL databases, Figure 12-2 may appear to be an overengineered approach to a task. However, the purpose of design patterns is to make the objects available for change and reuse. Each of the concrete strategies is encapsulated so that any changes (maintaining the implemented interfaces) will not crash the system. Even from the perspective of this minimal strategy, it should be easy to see how the behaviors in the concrete strategies are open to a wide range of implementations.
The Client
class makes requests through
the Context
, creating a concrete strategy. The
request for the different strategies is accomplished with a set of
methods. The following two lines are key in the request:
$context=new Context(new ConcreteStrategy()); $context->algorithm();
Each Client
method provides the
name of the concrete strategy to implement, and algorithm()
is a Context
method implemented in the concrete
strategies. This process reveals how
polymorphism works. Each request for the concrete method’s algorithm
is through a Context
instance, and so
the requests for all of them look exactly alike: $context->algorithm()
. However, the
Client
instantiates the Context
with a concrete strategy as an
argument. That argument allows the Context
to use the requested concrete strategy
by implementing the concrete strategy’s implementation of the algorithm()
method. In this way, the Strategy
pattern lets the algorithm vary independently from the clients that use
it. Instead of having several different client classes, several
different trigger scripts use the same client in the following
example:
<?php class Client { public function insertData() { $context=new Context(new DataEntry()); $context->algorithm(); } public function findData() { $context=new Context(new SearchData()); $context->algorithm(); } public function showAll() { $context=new Context(new DisplayData()); $context->algorithm(); } public function changeData() { $context=new Context(new UpdateData()); $context->algorithm(); } public function killer() { $context=new Context(new DeleteRecord()); $context->algorithm(); } } ?>
In order to fire the methods for the different concrete strategies (encapsulated algorithms), the HTML calls one of the following PHP trigger scripts:
<?php //insertTrigger.php function __autoload($class_name) { include $class_name . '.php'; } $trigger=new Client(); $trigger->insertData(); ?> ------------- <?php //displayTrigger.php function __autoload($class_name) { include $class_name . '.php'; } $trigger=new Client(); $trigger->showAll(); ?> ------------- <?php //findTrigger.php function __autoload($class_name) { include $class_name . '.php'; } $trigger=new Client(); $trigger->findData(); ?> ------------- <?php //updateTrigger.php function __autoload($class_name) { include $class_name . '.php'; } $trigger=new Client(); $trigger->changeData(); ?> ------------- <?php //killTrigger.php function __autoload($class_name) { include $class_name . '.php'; } $trigger=new Client(); $trigger->killer(); ?>
The form in the HTML document calls each PHP trigger separately. The request from the trigger script is passed on to the client, which uses one method for each request. In Strategy design patterns, the client typically creates and passes a concrete object to the context. However, the request origins lie in the HTML document:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Test</title> </head> <body> Insert<br /> <form name="insert" action="insertTrigger.php" method="post"> <input type="text" name="data"> <br /> <input type="submit" value="Insert"> </form> <br /> Find Data <form name="find" action="findTrigger.php" method="post"> <input type="text" name="data"> <br /> <input type="submit" value="Find"> </form> <br /> Display All Data <form name="display" action="displayTrigger.php" method="post"> <input type="submit" value="Show all data"> </form> <br /> Update Data <form name="change" action="updateTrigger.php" method="post"> <input type="text" name="data"> <br /> <input type="submit" value="Change data in record"> </form> <br /> Delete Record <form name="killer" action="killTrigger.php" method="post"> <input type="text" name="data"> <br /> <input type="submit" value="Delete Record"> </form> </body> </html>
One of the tricky elements in this arrangement is protecting the
$_POST
data by using the mysqli->real_escape_string()
method for
extracting the data values sent from the HTML document. It would be
possible to include an extra variable in all posts to indicate a
concrete strategy request method that the client will use and then make
the selection without using a gaggle of trigger scripts. The MySQL
connection could be made in the client, making it possible to extract
the data, close the connection, and then pass it on to the concrete
strategy, where a second connection could be opened to process the
request through the appropriate strategy. However, this example attempts
to keep the focus on design patterns and not abandoning all security
issues. One result is the separate trigger scripts for fulfilling a
request.
In the State design, the Context
class acts as a “track keeper”; it
keeps track of the current state. In the Strategy design, the Context
has a much different function. It
serves to separate a request from a concrete strategy, thereby allowing
the strategy and request to act independently of one another. It
represents another form of loose binding between request and
consequence. At the same time, it facilitates a request from the
Client
.
The Context
is not an interface
(of either the abstract class or interface variety), but it is
aggregated with the Strategy interface. The Gang of Four specify the
following characteristics:
It is configured with a concrete strategy object. (See how the
Client
class instantiates
Context
in The Client and the Trigger Scripts.)
It maintains a reference to a Strategy object.
It may define an interface that lets the Strategy access its data.
In the following listing, you can see these features of a Context
class:
<?php class Context { private $strategy; public function __construct(IStrategy $strategy) { $this->strategy = $strategy; } public function algorithm() { $this->strategy->algorithm(); } } ?>
First, the constructor function expects an implementation of
IStrategy
as a parameter. Second, it
maintains a reference to a Strategy object through an encapsulated
(private visibility) property, $strategy
. The $strategy
property receives its instance from
the constructor parameter that will be an instance of a concrete
strategy. Third, the algorithm()
method implements IStrategy
method,
also named algorithm()
as implemented
by the concrete strategy selected through the Client
. Because the Context
and IStrategy
make up an
aggregation, the Context
has certain features of an abstract
class or interface. In fact, a Context may be best understood in the
aggregation. In looking at the Strategy interface, IStrategy
, you can see the single method to be
implemented is algorithm()
:
<?php interface IStrategy { public function algorithm(); } ?>
Each concrete strategy can implement the method in any fashion required.
The encapsulated family of algorithms that makes up the concrete strategies provides a mockup of possible strategies. The point in this minimalist example is to see how the different participants in the Strategy design pattern work in concert. In this section, you will see a fully implemented example.
The five concrete strategies include the following classes:
DataEntry
DisplayData
SearchData
UpdateData
DeleteData
Each of these concrete strategies represents typical algorithms used with PHP and MySQL.
This first strategy mockup represents entering data into a table:
<?php class DataEntry implements IStrategy //DataEntry.php { public function algorithm() { $hookup=UniversalConnect::doConnect(); $test = $hookup->real_escape_string($_POST['data']); echo "This data has been entered: " . $test . "<br/>"; } } ?>
The $_POST['data']
is not
used in this example because the algorithm displays only the string
“Here’s all the data!!” which was assigned to the variable $test
as a string literal:
<?php //DisplayData.php class DisplayData implements IStrategy { public function algorithm() { $hookup=UniversalConnect::doConnect(); $test = "Here's all the data!!"; echo $test . "<br/>"; } } ?>
Here the search term is in the $_POST['data']
and passed to the $test
variable:
<?php //SearchData.php class SearchData implements IStrategy { public function algorithm() { $hookup=UniversalConnect::doConnect(); $test = $hookup->real_escape_string($_POST['data']); echo "Here's what you were looking for " . $test . "<br/>"; } } ?>
The “new” data is in the $_POST['data']
and passed to the $test
variable. In an actual implementation,
the name of the field would likely be included as well:
<?php //UpdateData.php class UpdateData implements IStrategy { public function algorithm() { $hookup=UniversalConnect::doConnect(); $test = $hookup->real_escape_string($_POST['data']); echo "Your new data is now " . $test . "<br/>"; } } ?>
Finally, using a unique identifier passed in $_POST['data']
and stored in $test
would be used to remove a record from
a table:
<?php //DeleteRecord.php class DeleteRecord implements IStrategy { public function algorithm() { $hookup=UniversalConnect::doConnect(); $test = $hookup->real_escape_string($_POST['data']); echo "The record " . $test . "has been deleted.<br/>"; } } ?>
All of the concrete strategies implement the same connection object (as seen in other chapters). The following interface would include names used by the actual program:
<?php //Filename: IConnectInfo.php interface IConnectInfo { const HOST ="localhost"; const UNAME ="alpha"; const PW ="beta"; const DBNAME = "gamma"; public function doConnect(); } ?>
The following connection class implements the
IConnectInfo
interface:
<?php include_once('IConnectInfo.php'); class UniversalConnect implements IConnectInfo { private static $server=IConnectInfo::HOST; private static $currentDB= IConnectInfo::DBNAME; private static $user= IConnectInfo::UNAME; private static $pass= IConnectInfo::PW; private static $hookup; public function doConnect() { self::$hookup=mysqli_connect(self::$server, self::$user, self::$pass, self::$currentDB); if(self::$hookup) { echo "Successful connection to MySQL:<br/>"; } elseif (mysqli_connect_error(self::$hookup)) { echo('Here is why it failed: ' . mysqli_connect_error()); } return self::$hookup; } } ?>
Using a connection class and separate interface allows for easy reuse and change. The only change is the values of the constants in the interface.
In the minimalist example in the previous section, you were
able to see all of the basic elements of a Strategy design pattern in PHP
using a MySQL database. To make a more robust example, this next example
adds functionality to the different strategies. It also adds a helper
class to deal with a secure movement of data from the HTML client to the
MySQL database. This means that the client is able to make secure requests
using data passed through the mysqli->real_escape_string($_POST['data'])
function. The Client
class could handle the security
itself, but that would give it an added responsibility beyond making the
requests.
Using the mysqli->real_escape_string($_POST['data'])
function to pass data securely between the HTML form and the PHP class
requires a MySQL connection, but once the connection has been opened and
the data securely passed, it can be closed again to free up resources
used by the connection.
Anticipating the different concrete strategies, the helper class
has unique methods for securing data for each of the concrete
strategies. A single method passes an array back to the Client
containing the required data for the
request. Figure 12-3
diagrams the relationship of the helper class to the Strategy.
The rest of the Strategy pattern is not shown in Figure 12-3 after the Context
class, but it follows the standard
class diagram shown in Figure 12-1. Also, the same
MySQL helper classes that are used for database requests are used with
the SecureData
class to create the
MySQL connection.
The SecureData
class has
methods for each of the concrete strategies that rely on data from the
HTML forms. The DisplayAll
concrete
strategy requests that all data be displayed, and so it needs no special
data passed from the HTML form:
<?php //Helper class //SecureData.php class SecureData { private $changeField; private $company; private $devdes; private $device; private $disappear; private $field; private $hookup; private $lang; private $newData; private $oldData; private $plat; private $style; private $term; //$dataPack will be an array private $dataPack; public function enterData() { $this->hookup=UniversalConnect::doConnect(); $this->company=$this->hookup->real_escape_string($_POST['company']); $this->devdes=$this->hookup->real_escape_string($_POST['devdes']); $this->lang= $this->hookup->real_escape_string($_POST['lang']); $this->plat= $this->hookup->real_escape_string($_POST['plat']); $this->style=$this->hookup->real_escape_string($_POST['style']); $this->device=$this->hookup->real_escape_string($_POST['device']); $this->dataPack=array( $this->company, $this->devdes, $this->lang, $this->plat, $this->style, $this->device ); $this->hookup->close(); } public function conductSearch() { $this->hookup=UniversalConnect::doConnect(); $this->field=$this->hookup->real_escape_string($_POST['field']); $this->term=$this->hookup->real_escape_string($_POST['term']); $this->dataPack=array( $this->field, $this->term ); $this->hookup->close(); } public function makeChange() { $this->hookup=UniversalConnect::doConnect(); $this->changeField=$this->hookup->real_escape_string($_POST['update']); $this->oldData=$this->hookup->real_escape_string($_POST['old']); $this->newData=$this->hookup->real_escape_string($_POST['new']); $this->dataPack=array( $this->changeField, $this->oldData, $this->newData ); $this->hookup->close(); } public function removeRecord() { $this->hookup=UniversalConnect::doConnect(); $this->disappear=$this->hookup->real_escape_string($_POST['delete']); $this->dataPack=array($this->disappear); $this->hookup->close(); } //Returns secure data as array to requesting Client public function setEntry() { return $this->dataPack; } } ?>
All of the methods except for setEntry()
generate an array named dataPack
. The setEntry()
method returns the current contents
of dataPack
. Depending on the
request, the SecureData
class
generates values placed into the array, which is passed back to the
Client
and becomes part of a request
to a concrete strategy through the algorithm()
method.
A second added feature is a change in the Strategy algorithm method. By adding an array as a parameter to the function, it is far more flexible in what it can handle. Each call to the algorithm function contains an array made up of data passed from the HTML form:
<?php interface IStrategy { const TABLENOW ="survey"; public function algorithm(Array $dataPack); } ?>
Likewise, a constant, TABLENOW
,
has been added to the interface. Because every concrete strategy uses
the same table in this implementation, using PHP’s capability of passing
on constants from an interface allows a loose and reusable code.
Obviously if different tables were to be used for the different concrete
strategies, the table references would have to be assigned in the
individual concrete strategies. Type hinting in the parameter forces an
array to be used as an argument.
The following script is used to create a table for the survey. Larger or smaller tables can be used with the Strategy design by altering the size of the array used in the concrete strategies:
<?php include_once('UniversalConnect.php'); class CreateTable { private $tableMaster; private $hookup; public function __construct() { $this->tableMaster="survey"; $this->hookup=UniversalConnect::doConnect(); $drop = "DROP TABLE IF EXISTS $this->tableMaster"; if($this->hookup->query($drop) === true) { printf("Old table %s has been dropped.<br/>",$this->tableMaster); } $sql = "CREATE TABLE $this->tableMaster ( id SERIAL, company NVARCHAR(40), devdes NVARCHAR(10), lang NVARCHAR(15), plat NVARCHAR(15), style NVARCHAR(20), device NVARCHAR(10), PRIMARY KEY (id))"; if($this->hookup->query($sql) === true) { printf("Table $this->tableMaster has been created successfully. <br/>"); } $this->hookup->close(); } } $worker=new CreateTable(); ?>
This particular table-creation class is used during the development and debugging cycle. Once you have your table the way you want it and want it installed on different systems, you can remove the following section:
$drop = "DROP TABLE IF EXISTS $this->tableMaster"; if($this->hookup->query($drop) === true) { printf("Old table %s has been dropped.<br/>",$this->tableMaster); }
and change the following code:
$sql = "CREATE TABLE $this->tableMaster (
to the following:
$sql = "CREATE TABLE IF NOT EXISTS $this->tableMaster (
In this way, the class will not remove any existing tables that may have data already stored in them.
The same UniversalConnect
class
is employed as with all of the other MySQL connections in this
chapter.
Given the job of the SecureData
helper class and the revised
IStrategy
interface to include a
parameter for the algorithm()
method,
Client
can more easily make requests
based on methods for the various requests from the HTML forms. Before
going on, take a look at the requests originating in the HTML forms. Two
forms are used: one for user input of survey data and one for viewing
the data stored in a MySQL table. All are very simple and represent
generic HTML forms. Both forms share a single CSS file:
@charset "UTF-8"; /*survey.css */ /* CSS Document */ /*B2B2B2,B2A1A1,666666,8FB299*/ body { background-color:#B2B2B2; color:#666666; font-family:Verdana, Geneva, sans-serif } h2 { font-family:"Arial Black", Gadget, sans-serif; color:#666666 } h3 { font-family:"Trebuchet MS", Arial, Helvetica, sans-serif; background-color:#8FB299; color:#666666 } th { text-align:left; background-color:#8FB299; color:#666666 }
The CSS is set for minimum differentiation.
First, the survey is a simple text entry plus several selections from radio forms:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="survey.css"> <meta charset="UTF-8"> <title>Programmer Profile Survey</title> </head> <body> <h2>Programmer Survey</h2> <form name="survey" action="insertTrigger.php" method="post"> <input type="text" name="company"> Company Name<br /> <h3> Primary Role</h3> <input type="radio" name="devdes" value="developer"> Developer<br /> <input type="radio" name="devdes" value="designer"> Designer<br /> <h3> Primary Programming Language</h3> <input type="radio" name="lang" value="PHP"> PHP<br /> <input type="radio" name="lang" value="C#/ASP.NET"> C# ASP.NET<br /> <input type="radio" name="lang" value="PERL"> PERL<br /> <input type="radio" name="lang" value="JavaScript"> JavaScript<br /> <input type="radio" name="lang" value="ActionScript 3.0"> ActionScript 3.0<br /> <h3> Primary Development/Design Platform</h3> <input type="radio" name="plat" value="WinPC"> Windows PC<br /> <input type="radio" name="plat" value="Mac"> Apple Macintosh<br /> <input type="radio" name="plat" value="Linux"> Linux<br /> <h3> Primary Programming Style</h3> <input type="radio" name="style" value="sequential"> Sequential<br /> <input type="radio" name="style" value="procedural"> Procedural<br /> <input type="radio" name="style" value="OOP"> Object Oriented Programming<br /> <input type="radio" name="style" value="design patterns"> Design Patterns <p /> <h3> Primary Platform Development/Design</h3> <input type="radio" name="device" value="Desktop"> Desktop<br /> <input type="radio" name="device" value="Tablet"> Tablet<br /> <input type="radio" name="device" value="Smartphone"> Smartphone <p /> <input type="submit" value="Create Profile" name="sender"> </form> </body> </html>
Figure 12-4 shows the survey opened in a tablet device.
A second HTML document provides an administrative tool for examining the table. Again, it is quite simple and used primarily for following the data from its origins in an HTML form through a Strategy design pattern:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="survey.css"> <meta charset="UTF-8"> <title>Administrative Module</title> </head> <body> <h2>Administrative Module</h2> <h3> Display all data</h3> <form name="allData" action="displayTrigger.php" method="post"> <input type="submit" value="Display Data" name="display"> </form> <form name="search" action="findTrigger.php" method="post"> <h3> Search Field</h3> <input type="radio" name="field" value="id"> ID<br /> <input type="radio" name="field" value="company"> Company<br /> <input type="radio" name="field" value="devdes"> Designer/Developer<br /> <input type="radio" name="field" value="lang"> Computer Language<br /> <input type="radio" name="field" value="plat"> Development Platform<br /> <input type="radio" name="field" value="style"> Programming Style<br /> <input type="radio" name="field" value="device"> Target Device <p /> <input type="text" name="term"> Term to find <p /> <input type="submit" value="Search" name="searcher"> </form> <form name="search" action="changeTrigger.php" method="post"> <h3> Change Field</h3> <input type="radio" name="update" value="id"> ID <br /> <input type="radio" name="update" value="company"> Company<br /> <input type="radio" name="update" value="devdes"> Designer/Developer<br /> <input type="radio" name="update" value="lang"> Computer Language<br /> <input type="radio" name="update" value="plat"> Development Platform<br /> <input type="radio" name="update" value="style"> Programming Style<br /> <input type="radio" name="update" value="device"> Target Device <p /> <input type="text" name="old"> Old Value <p /> <input type="text" name="new"> New Value <p /> <input type="submit" value="Change Value" name="changer"> </form> <h3> Delete Record</h3> <form name="killer" action="killTrigger.php" method="post"> <input type="text" name="delete" size=3> Number of Record to Delete <p /> <input type="submit" value="Permanently Delete Record" name="doa"> </form> </body> </html>
Figure 12-5 shows the UI displayed on a tablet device.
Both UIs use a mobile layout of a single column that can be adjusted for viewing on mobile phones with Internet capabilities.
Each button in the two HTML documents represents a separate form.
Each form calls a trigger file that in turn instantiates the
Client
class and the appropriate method required to
carry out the requested task.
The Client
has no
constructor function, but instead it has several methods available for
different requests. The methods are similar to the minimalist example,
but they do more with the help of a SecureData
helper class discussed previously,
in A Data Security Helper Class.
First, review what the SecureData
class does, and then take a look at
the Client
:
<?php //Client.php class Client { public function insertData() { $secure=new SecureData(); $context=new Context(new DataEntry()); $secure->enterData(); $context->algorithm($secure->setEntry()); } public function findData() { $secure=new SecureData(); $context=new Context(new SearchData()); $secure->conductSearch(); $context->algorithm($secure->setEntry()); } public function showAll() { $dummy=array(0); $context=new Context(new DisplayAll()); $context->algorithm($dummy); } public function changeData() { $secure=new SecureData(); $context=new Context(new UpdateData()); $secure->makeChange(); $context->algorithm($secure->setEntry()); } public function killer() { $secure=new SecureData(); $context=new Context(new DeleteRecord()); $secure->removeRecord(); $context->algorithm($secure->setEntry()); } } ?>
With the exception of the showAll()
method, all of the methods in the
Client
first instantiate the SecureData
class. Then the methods create a
context object, using the concrete method as a parameter. Next, the
SecureData
object calls the
appropriate method related to the concrete strategy to create the
required array. Finally, the Client
method calls the
Context->algorithm()
using the
array, $secure->setEntry()
returned from the SecureData
class as
an argument. The array contents depend on user input sent from the HTML
form and the type of strategy requested.
The Gang of Four point out that all concrete strategy classes
share the same interface whether they are used or not.
Therefore all concrete strategy classes must implement the method in the
Strategy interface (the algorithm()
method in IStrategy
). However, not
all concrete strategies may need the algorithm and certainly not in the
same way.
To some extent, you can see this in the showAll()
method in the Client
class. Instead of using an array
returned from the SecureData
class,
it creates a dummy array containing it and uses it as a parameter in the
Context->algorithm()
. This is one
way to meet the requirements of the IStrategy
interface to include an array as a
parameter.
Compared to the minimalist example in the first part of
this chapter, the Context
class is
little changed other than adding a parameter to the algorithm()
method required by the updated
IStrategy
interface. Because of the
aggregate relationship between the Context
class and IStrategy
, the Context
class must hold a reference to
IStrategy
. As in the minimalist
example, the Context
is created with
a concrete strategy object. That part is unchanged. However, it also
includes a method that instantiates the concrete strategy’s
implementation of the algorithm()
method:
<?php class Context { private $strategy; private $dataPack; public function __construct(IStrategy $strategy) { $this->strategy = $strategy; } public function algorithm(Array $dataPack) { $this->dataPack=$dataPack; $this->strategy->algorithm($this->dataPack); } } ?>
The name of the Context
class
method is also “algorithm” and requires an array parameter. The common
algorithm
name is to emphasize the
aggregate relationship between the context and strategy participants.
If this is confusing, you might want to rename the method
contextAlgorithm
to differentiate it from the
IStrategy algorithm()
method.
The Context
also includes an
additional property, $dataPack
,
reflecting the name of the array that is being passed through the
Context algorithm()
method. It is
then passed to the concrete strategy’s algorithm()
method.
The whole purpose of passing data through an array
to the concrete strategies is to allow the different
strategies to respond to different requests. It gives flexibility to the
design because of the wide range of data that can be passed with an
array. As you can see in the following concrete strategy classes, each
implements the IStrategy algorithm()
method using the data passed through the methods array parameter.
All of the concrete classes use the same UniversalConnec
t
class as
used in the minimalist strategy example. The table name is stored as a
constant (TABLENOW
) in the IStrategy
interface.
Of all of the concrete strategies, the
DataEntry
class uses the largest array. That’s
because it must insert all of the data from the HTML survey:
<?php class DataEntry implements IStrategy //DataEntry.php { private $tableMaster; private $dataPack; private $hookup; private $sql; public function algorithm(Array $dataPack) { $this->dataPack=$dataPack; $comval=$this->dataPack[0]; $devdesval=$this->dataPack[1]; $langval=$this->dataPack[2]; $platval=$this->dataPack[3]; $styleval=$this->dataPack[4]; $deviceval=$this->dataPack[5]; $this->tableMaster=IStrategy::TABLENOW; $this->hookup=UniversalConnect::doConnect(); $this->sql = "INSERT INTO $this->tableMaster ( company, devdes, lang, plat, style, device ) VALUES ( '$comval', '$devdesval', '$langval', '$platval', '$styleval', '$deviceval' )"; if($this->hookup->query($this->sql)) { printf("Successful data entry for table: $this->tableMaster <br/>"); } elseif ( ($result = $this->hookup->query($this->sql))===false ) { printf("Invalid query: %s <br/> Whole query: %s <br/>", $this->hookup->error, $this->sql); exit(); } $this->hookup->close(); } } ?>
The only conditional statements are those used in standard MySQL statements. The general algorithm that makes up the core of the class uses none.
This is the class that is passed a dummy array. As you can see, it simply uses a general algorithm for sending data from the table to the screen:
<?php //DisplayAll.php class DisplayAll implements IStrategy { private $tableMaster; private $hookup; public function algorithm(Array $dataPack) { $this->tableMaster=IStrategy::TABLENOW; $this->hookup=UniversalConnect::doConnect(); //Create Query Statement $sql ="SELECT * FROM $this->tableMaster"; //Conditional statement in MySQL command if ($result = $this->hookup->query($sql)) { printf("Select returned %d rows.<p />", $result->num_rows); echo "<link rel='stylesheet' href='survey.css'>"; echo "<table>"; while ($finfo = mysqli_fetch_field($result)) { echo "<th> {$finfo->name}</th>"; } echo "</tr>\n"; while($row=mysqli_fetch_row($result)) { echo "<tr>"; foreach($row as $cell) { echo "<td>$cell</td>"; } echo "</tr>"; } echo"</table>"; $result->close(); } $this->hookup->close(); } } ?>
A table aids in the data display but is by no means optimized. Rather, with the focus on implementing the design pattern, the format is kept simple. Figure 12-6 shows the output.
The search algorithm selects a specified value from a specified
field. The field name and search value are passed through the array as
an argument in the algorithm()
method. A match results in the record being displayed, while nothing
is displayed when there’s no match:
<?php //SearchData.php class SearchData implements IStrategy { private $tableMaster; private $dataPack; private $hookup; private $sql; public function algorithm(Array $dataPack) { $this->tableMaster=IStrategy::TABLENOW; $this->hookup=UniversalConnect::doConnect(); $this->dataPack=$dataPack; $field=$this->dataPack[0]; $term=$this->dataPack[1]; $this->sql = "SELECT * FROM $this->tableMaster WHERE $field='$term'"; //Conditional statement in MySQL query for data output if ($result = $this->hookup->query($this->sql)) { echo "<link rel='stylesheet' href='survey.css'>"; echo "<table>"; while($row=mysqli_fetch_row($result)) { echo "<br />"; echo "<tr>"; foreach($row as $cell) { echo "<td>$cell</td>"; } echo "</tr>"; } echo "</table>"; $result->close(); } $this->hookup->close(); } } ?>
A more sophisticated algorithm and display design can be easily
substituted. If more data is required, it is a simple matter to change
the data generated in the HTML document and placed into an array in
the SecureData
class. This can be
done without affecting any other components of the program, and this
is in no small part the sine
qua non of design patterns overall. Figure 12-7 shows a search
for “designer” in the Designer/Developer field.
To change the value in a current field, this implementation requires only three elements: the name of the field, the old value, and the new value. The algorithm is flexible, and like all other concrete strategies, this one can be modified to reflect requirements:
<?php //UpdateData.php class UpdateData implements IStrategy { private $tableMaster; private $dataPack; private $hookup; private $sql; public function algorithm(Array $dataPack) { $this->tableMaster=IStrategy::TABLENOW; $this->hookup=UniversalConnect::doConnect(); $this->dataPack=$dataPack; $changeField=$this->dataPack[0]; $oldData=$this->dataPack[1]; $newData=$this->dataPack[2]; $this->sql = "UPDATE $this->tableMaster SET $changeField='$newData' WHERE $changeField='$oldData'"; //Conditional statement in MySQL query for error checking if ($result = $this->hookup->query($this->sql)) { echo "$changeField changed from $oldData to: $newData"; } else { echo "Change failed: " . $hookup->error; } } } ?>
The output informs the user that a change has been made.
The final concrete strategy removes a record and requires only a single element in an array to pass on the record number to delete. Because the table has been created with an automatic numbering system, the identification number is an integer:
<?php //DeleteRecord.php class DeleteRecord implements IStrategy { private $tableMaster; private $dataPack; private $hookup; private $sql; public function algorithm(Array $dataPack) { $this->tableMaster=IStrategy::TABLENOW; $this->hookup=UniversalConnect::doConnect(); $this->dataPack=$dataPack; $destroy=$this->dataPack[0]; $destroy= intval($destroy); $this->sql = "DELETE FROM $this->tableMaster WHERE id='$destroy'"; //Conditional statement in MySQL query for error checking if ($result = $this->hookup->query($this->sql)) { echo "Record #$destroy removed from table: $this->tableMaster"; } else { echo "Removal failed: " . $hookup->error; } } } ?>
The class and accompanying concrete strategy are simple, and those who want to add a more robust algorithm can do so without disrupting the rest of the program.
The Strategy pattern is so flexible that not only can changing the algorithm change one implementation, but also the pattern itself has more than one implementation. On the one hand, this chapter showed how the minimalist version of the Strategy design pattern could call up different algorithms that worked independent of data outside of the concrete strategy. On the other hand, the second example used parameters that passed secure data to the concrete strategies.
The Gang of Four point out that one approach is to have the Context pass data in parameters to Strategy operations. That is exactly what the second example did. This approach takes the data to the strategy, whereas keeping the Context and Strategy decoupled may also pass data to the Strategy that it does not need. The approach to this dilemma was to use an array to add flexibility in what was passed to the Strategy. This included an empty array.
The particular type of Strategy implementation depends on the needs of the particular algorithm and what it requires. Some implementations of the Strategy involve storing a reference to its context, which eliminates the need to pass anything. However, in so doing, it more tightly couples the Context and Strategy.
A further issue to consider is the number of objects the Strategy pattern generates in the form of concrete strategies. As you’ve seen in the examples, quite a few objects (classes) are built to handle the different requests made in a simple MySQL survey run by PHP. More could be built. However, that may not be as big a problem in view of the advantages in reuse and change of the pattern. Design patterns are built for speed in managing an application, but not executing code. A developer with a well-organized Strategy pattern can easily optimize and reoptimize the encapsulated algorithms without it falling in around his head. So the speed is in the reuse and change time, and the cost of additional objects is a small expense.