Five more PHP design patterns

The book Design Patterns introduced me to the notion there could be such things. At the time, I was still learning object orientation (OO), so there were many concepts in the book I found difficult to grasp. However, as I became more comfortable with OO concepts — particularly the use of interfaces and inheritance — I began to see the real value in design patterns. As an application developer, you can have a lifelong career without ever knowing what any of the patterns are called or how or when they're used. However, I've found that a good working knowledge of these patterns, as well as those introduced in the developerWorks article "Five common PHP design patterns" (see Resources ), allows you to do two things:

Enable high-bandwidth conversations
If you know about design patterns, you'll be able to build solid OO applications faster. But when your entire development team knows the various patterns, you can suddenly have very high-bandwidth conversations. You no longer need to talk about all the classes you would use here or there. Instead, you can talk in terms of patterns to each other. "Well, I'm referencing a singleton here, then using an iterator to go through a collection of my objects, and ... " is much, much faster than going through the classes, methods, and interfaces that make up these patterns. This efficiency in communication alone can be worth taking time to go through a couple of sessions together, as a team, to study patterns.
Reduce painful lessons
Each design pattern describes a proven way to solve a common problem. Therefore, you don't need to worry about whether your design is the right one, so long as you've chosen the pattern that provides the advantages you need.

Pitfalls

There is a saying that goes something like this: "When you're holding a hammer, everything looks like a nail." When you find a pattern you think is great, you may try to use it anywhere and everywhere, even in places you shouldn't. Remember that you must consider the usage goals of the patterns you're learning and try not to force patterns into parts of your application just for the sake of using them.

This article covers five patterns you can use to improve your PHP code. Each pattern covers a specific scenario. The PHP code for these patterns is available in the Download section.

Requirements

To get the most out of this article and use the examples, install the following on your computer:

  • PHP V5 or later (this article was written with PHP V5.2.4)
  • An extraction program, such as WinZIP (to extract the downloadable code archive)

Note: Although you don't absolutely need to have an editor other than a plain text editor, I find that it really helps to have syntax highlighting and syntax error-correcting. The examples in this article were written using the Eclipse PHP Development Tools (PDT).


The adapter pattern

Use the adapter pattern when you need to convert an object of one type to an object of another type. Typically, developers handle this process through a bunch of assignment code, as shown in Listing 1. The adapter pattern is a nice way to clean this type of code up and reuse all your assignment code in other places. Also, it hides the assignment code, which can simplify things quite a bit if you're also doing some formatting along the way.


Listing 1. Using code to assign values between objects

                
class AddressDisplay
{
private $addressType;
private $addressText;

public function setAddressType($addressType)
{
$this->addressType = $addressType;
}

public function getAddressType()
{
return $this->addressType;
}

public function setAddressText($addressText)
{
$this->addressText = $addressText;
}

public function getAddressText()
{
return $this->addressText;
}
}

class EmailAddress
{
private $emailAddress;

public function getEmailAddress()
{
return $this->emailAddress;
}

public function setEmailAddress($address)
{
$this->emailAddress = $address;
}
}

$emailAddress = new EmailAddress();
/* Populate the EmailAddress object */
$address = new AddressDisplay();
/* Here's the assignment code, where I'm assigning values
from one object to another... */

$address->setAddressType("email");
$address->setAddressText($emailAddress->getEmailAddress());

 

This example uses an AddressDisplay object to display an address to a user. The AddressDisplay object has two parts: the type of address and a formatted address string.

After implementing the pattern (see Listing 2), the PHP script no longer needs to worry about exactly how the EmailAddress object is turned into the AddressDisplay object. That's a good thing, especially if the AddressDisplay object changes or the rules that govern how an EmailAddress object is turned into an AddressDisplay object change. Remember, one of the main benefits of designing your code in a modular fashion is to take advantage of having to change as little code as possible if something in the business domain changes or you need to add a new feature to the software. Think about this even when you're doing mundane tasks, such as assigning values from properties of one object to another.


Listing 2. Using the adapter pattern

                
class EmailAddressDisplayAdapter extends AddressDisplay
{
public function __construct($emailAddr)
{
$this->setAddressType("email");
$this->setAddressText($emailAddr->getEmailAddress());
}
}

$email = new EmailAddress();
$email->setEmailAddress("user@example.com");

$address = new EmailAddressDisplayAdapter($email);

echo($address->getAddressType() . "/n") ;
echo($address->getAddressText());

 

Figure 1 shows a class diagram of the adapter pattern.


Figure 1. Class diagram of the adapter pattern
Class diagram of the adapter pattern

Alternate method

An alternate method of writing an adapter — and one that some prefer — is to implement an interface to adapt behavior, rather than extending an object. This is a very clean way of creating an adapter and doesn't have the drawbacks of extending the object. One of the disadvantages of using the interface is that you need to add the implementation into the adapter class, as shown in Figure 2.


Figure 2. The adapter pattern (using an interface)
The adapter pattern (using an interface)


The iterator pattern

The iterator pattern provides a way to encapsulate looping through a collection or array of objects. It is particularly handy if you want to loop through different types of objects in the collection.

Look back at the e-mail and physical address example in Listing 1. Before adding an iterator pattern, if you're looping through the person's addresses, you might loop through the physical addresses and display them, then loop through the person's e-mail addresses and display them, then loop through the person's IM addresses and display those. That's some messy looping!

Instead, by implementing an iterator, all you have to do is call while($itr->hasNext()) and deal with the next item $itr->next() returns. An example of one of the iterators is shown in Listing 3. An iterator is powerful because you can add new types of items through which to iterate, and you don't have to change the code that loops through the items. In the Person example, for instance, you could add an array of IM addresses; simply by updating the iterator, you don't have to change any code that loops through the addresses for display.


Listing 3. Using the iterator pattern to loop through objects

                
class PersonAddressIterator implements AddressIterator
{
private $emailAddresses;
private $physicalAddresses;
private $position;

public function __construct($emailAddresses)
{
$this->emailAddresses = $emailAddresses;
$this->position = 0;
}

public function hasNext()
{
if ($this->position >= count($this->emailAddresses) ||
$this->emailAddresses[$this->position] == null) {
return false;
} else {
return true;
}
}

public function next()
{
$item = $this->emailAddresses[$this->position];
$this->position = $this->position + 1;
return $item;
}

}

 

If the Person object is modified to return an implementation of the AddressIterator interface, the application code that uses the iterator doesn't need to be modified if the implementation is extended to loop through additional objects. You can use a compound iterator that wraps the iterators that loop through each type of address like the one listed in Listing 3. An example of this is available (see Download ).

Figure 3 shows a class diagram of the iterator pattern.


Figure 3. Class diagram of the iterator pattern
Class diagram of the iterator pattern


The decorator pattern

Consider the code sample in Listing 4. The purpose of this code is to add a bunch of features onto a car for a Build Your Own Car site. Each car model has more features and an associated cost. With only two models, it would be fairly trivial to add these features with if then statements. However, if a new model came along, you'd have to go back through the code and make sure the statements worked for the new model.


Listing 4. Using the decorator pattern to add features

                
require('classes.php');

$auto = new Automobile();

$model = new BaseAutomobileModel();

$model = new SportAutomobileModel($model);

$model = new TouringAutomobileModel($model);

$auto->setModel($model);

$auto->printDescription();

 

Enter the decorator pattern, which allows you to add this functionality onto the AutomobileModel in a nice, clean class. Each class remains concerned only about its price and options and how they're added to the base model.

Figure 4 shows a class diagram for the decorator pattern.


Figure 4. Class diagram of the decorator pattern
Class diagram of the decorator pattern

An advantage of the decorator pattern is that you can easily tack on more than one decorator to the base at a time.

If you've done much work with stream objects, you have used a decorator. Most stream constructs, such as an output stream, are decorators that take a base input stream, then decorate it by adding additional functionality — like one that inputs streams from files, one that inputs streams from buffers, etc.


The delegate pattern

The delegate pattern provides a way of delegating behavior based on different criteria. Consider the code in Listing 5. This code contains several conditions. Based on the condition, the code selects the appropriate type of object to handle the request.


Listing 5. Using conditional statements to route shipping requests

                
pkg = new Package("Heavy Package");
$pkg->setWeight(100);

if ($pkg->getWeight() > 99)
{
echo( "Shipping " . $pkg->getDescription() . " by rail.");
} else {
echo("Shipping " . $pkg->getDescription() . " by truck");
}

 

With a delegate pattern, an object internalizes this routing process by setting an internal reference to the appropriate object when a method is called, like useRail() in Listing 6. This is especially handy if the criteria change for handling various packages or if a new type of shipping becomes available.


Listing 6. Using the delegate pattern to route shipping requests

                
require_once('classes.php');

$pkg = new Package("Heavy Package");
$pkg->setWeight(100);

$shipper = new ShippingDelegate();

if ($pkg->getWeight() > 99)
{
$shipper->useRail();
}

$shipper->deliver($pkg);

 

The delegate provides the advantage that behavior can change dynamically by calling the useRail() or useTruck() method to switch which class handles the work.

Figure 5 shows a class diagram of the delegate pattern.


Figure 5. Class diagram of the delegate pattern
Class diagram of the delegate pattern


The state pattern

The state pattern is a similar to the command pattern, but the intent is quite different. Consider the code below.


Listing 7. Using code to build a robot

                
class Robot
{

private $state;

public function powerUp()
{
if (strcmp($state, "poweredUp") == 0)
{
echo("Already powered up.../n");
/* Implementation... */
} else if ( strcmp($state, "powereddown") == 0) {
echo("Powering up now.../n");
/* Implementation... */
}
}

public function powerDown()
{
if (strcmp($state, "poweredUp") == 0)
{
echo("Powering down now.../n");
/* Implementation... */
} else if ( strcmp($state, "powereddown") == 0) {
echo("Already powered down.../n");
/* Implementation... */
}
}

/* etc... */

}

 

In this listing, the PHP code represents the operating system for a powerful robot that turns into a car. The robot can power up, power down, turn into a robot when it's a vehicle, and turn into a vehicle when it's a robot. The code is OK now, but you see that it can become complex if any of the rules change or if another state comes into the picture.

Now look at Listing 8, which has the same logic for handling the robot's states, but this time puts the logic into the state pattern. The code in Listing 8 does the same thing as the original code, but the logic for handling states has been put into one object for each state. To illustrate the advantages of using the design pattern, imagine that after a while, these robots have discovered that they shouldn't power down while being in robot mode. In fact, if they power down, they must change to vehicle mode first. If they're already in vehicle mode, the robot just powers down. With the state pattern, the changes are pretty trivial.


Listing 8. Using the state pattern to handle the robot's state

                
$robot = new Robot();
echo("/n");
$robot->powerUp();
echo("/n");
$robot->turnIntoRobot();
echo("/n");
$robot->turnIntoRobot(); /* This one will just give me a message */
echo("/n");
$robot->turnIntoVehicle();
echo("/n");



Listing 9. Small changes to one of the state objects

                
class NormalRobotState implements RobotState
{
private $robot;

public function __construct($robot)
{
$this->robot = $robot;
}

public function powerUp()
{
/* implementation... */
}
public function powerDown()
{
/* First, turn into a vehicle */
$this->robot->setState(new VehicleRobotState($this->robot));
$this->robot->powerDown();

}

public function turnIntoVehicle()
{
/* implementation... */
}

public function turnIntoRobot()
{
/* implementation... */
}
}

 

Something that doesn't appear obvious when looking at Figure 6 is that each object in the state pattern has a reference to the context object (the robot), so each object can advance the state onto the appropriate one.


Figure 6. Class diagram of the state pattern
Class diagram of the state pattern


Summary

Using design patterns in your PHP code is one way to make your code more readable and maintainable. By using established patterns, you benefit from common design constructs that allow other developers on a team to understand your code's purpose. It also allows you to benefit from the work done by other designers, so you don't have to learn the hard lessons of design ideas that don't work out.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值