PHP Decorator Design Pattern: Accessorizing Your Classes

decorator design pattern

Decorating an Object

Adding Just the Flourishes You Need

How would your like your concrete classes to be like an unadorned Christmas tree? When you need an ornament, you put it on. You can put on several of the same type, all different types and when they’re no longer needed, you can take them off. Your central object (class) is unchanged and you’re not processing stuff you’re not using. When you need it; you just pop it on like an ornament on a tree. Further, you can decorate different components with the same ornaments.

When would you use such a pattern? Consider setting up an order form. Each order is an object, and you decorate your order with other objects the user wants to buy. When I buy a computer, I accessorize it with with added memory, a Webcam, a USB hub and anything else that I think I need. However, the store doesn’t have to have a separate computer for every possible combination that users may want to buy. They can just have a few and let the user decorate them anyway she wants. Further, the object you want does not have to drag every option with it—just the ones the buyer wants. You can play the test case and download all of the files using the following two buttons:
Download

The Structure

To kick things off, let’s take a look at the class diagram that the Gang of Four devised. It is one of the most interesting because an abstract class is subclassed from another abstract class (among other things) as can be seen in Figure 1:

Figure 1:Decorator Class Diagram

In looking at what varies, we find that the variation is responsibilities of an object without sub-classing—in other words, use delegation. However, you can see that both the Decorator class and the Concrete Components and Decorators are all sub-classed from Abstract classes. Isn’t that inheritance instead of composition? Of all of the things that can vary in a program structure, those assigned to the Decorator still need further clarification. As you will see in the next section, the Decorator seems to even contradict its own element of variation because it double-subclasses. However, once everything is straightened out, you will find the Decorator example we use to be very simple to implement and even use in a real live work situation.

An Abstract Class as a Subclass

One of the most unusual elements of the Decorator pattern is the fact that the abstract class, Decorator, is a subclass of the base abstract class, Component. The concrete component classes derived directly from the Component class act in a very similar way as the concrete decorator classes. This is because they have an almost identical interface. However, the concrete decorator classes wrap a Component reference inherited from the Decorator.

Developers have employed different ways to implement the Decorator design pattern, but I prefer to use abstract classes for both the component and decorator.The wrapper is the abstract Decorator class. First, in our design all of the decorators wrap concrete components using the IComponent Type Hinting. This helps establish the correct interface. You will see:

private  $computer;
public function __construct(IComponent $computer) 
{
	$this->computer = $computer;
}

That’s possible because the abstract Decorator class extends the abstract component class named IComponent. Even though the object is instantiated as a concrete Component, the same interface is inherited in the concrete decorators because their parent class (Decorator) inherits the interface of the IComponent abstract class.

Gamma, Helm, Johnson and Vlissides point out,

The decorator conforms to the interface of the component it decorates so that its presence is transparent to the component’s clients. (p. 175)

That sounds something like recursion—that’s because it is recursion. The purpose, though, is to add the decorator’s features to the component without changing the component. The decorations occur at run-time giving the design a good deal of flexibility through delegation and loose coupling.

Note: I use the name IComponent for the name of the abstract class. That’s because it reminds us that the class is abstract and all we want from it is its interface. I could have used an Interface, especially since I used abstract methods; however, I like the idea of using an abstract class because they afford a bit more flexibility. A common convention is to name interfaces with the initial capital “I” but the same practice has been applied to abstract classes as well.

Second, the design suggests that the concrete decorators be instantiated by wrapping the component instance. That would mean that the constructors would have to override the parent class constructor function. Well, PHP does not have override statements, but with an abstract class, there’s nothing to override. I don’t suppose there’s anything wrong with that, but I saw nothing to be gained from it. In their work, the Freemans (Head First Design Patterns) similarly did not include the wrapper in the abstract Decorator, even though Java does have an abstract class modifier. In any event, the decorator wraps the component and that allows the component and decorator elements to share much of the design’s structure.

Using the Decorator in an Online Computer Store

To demonstrate how to use the Decorator design pattern, we’ll start off with a simple set of classes that represent components to be decorated (computer) and their decorators (accessories.) The choices of computers will be limited to a Dell and an Apple Macintosh, and they will share the same set of decorators: a terabyte of added hard drive, a USB hub, and a big monitor.

Abstract Classes

The abstract classes are made up of IComponent and Decorator. The Decorator wraps the IComponent with an aggregation relationship. In order for this kind of wrapping, the two must share the same interface, and so the component classes and decorator classes too share the same interfaces albeit with different implementations. The Decorator extends the IComponent, and so all of the Decorator child classes (concrete decorators) do as well. So first, we have the IComponent abstract class with two methods. (An interface may have served as well as an abstract class, but I was interested to see if we could build a PHP Decorator example using abstract classes since the original example by GoF used them for the component and decorator interface. Also, I had not seen other PHP examples of a Decorator that used abstract classes.)

<?php
abstract class IComponent
{
	abstract public function getComputer();
	abstract public function price();
}
?>

Because the Decorator acts as a wrapper for the IComponent it conforms to the IComponent’s interface, making the decoration transparent. As you can see, there’s not much we need to do with the Decorator other than extend the IComponent. The getDescription() method represents the potential of other methods. (If you comment it out, it won’t affect the design.)

<?php
abstract class Decorator extends IComponent
{
	//public function getDescription() { }
}
?>

The Concrete Components: Wrap Me Up!

The concrete components are the “wrappees” (elements that get wrapped) and as such they don’t have to do much. In this example, they have a description and a price. The description differentiates them from other objects (computers) and price returns what they’ll set you back.

///Concrete Components
<?php
class Dell extends IComponent 
{
	private $computer;
	public function __construct() 
	{
		$this->computer="Dell Computer: ";
	}
 
	public function getComputer()
	{
		return $this->computer;
	}
	public function price() 
	{
		return 599;
	}
}
?>
///Concrete Component
<?php
class Apple extends IComponent
{
	public $computer;
	public function __construct()
	{
		$this->computer="Apple Macintosh: ";
	}
 
	public function getComputer()
	{
		return $this->computer;
	}
	public function price()
	{
		return 799;
	}
}
?>
 
As you can see we have two concrete components: one for an Apple and one for a Dell computer. (You HP folks can relax; it's easy to add concrete components.)
 
<strong>The Concrete Decorators: <em>Boa-Constructors</em></strong>
 
The actual wrappers are the concrete decorators. Like the  concrete components they have a description and a price. However, they also wrap a component and they return not only their own price but <em>also the value of everything that they wrap</em>. Concrete decorators wrap concrete component instances in their constructors. (Think of "boa-constructors" wrapping components!)
 
An important feature of the concrete decorators is that they include an IComponent as type hinting. This is truly programming to the interface. Since the decorators are also IComponent types, when a wrapped component is wrapped in a decorator, there's no conflict. They have the same interface. (Please note that the price() method has both a "+"  and a "." operator. The former is for adding the value of the decorator's value and the component's value, and the latter is for concatenation.
 
<pre lang="php" colla="+">
///Concrete Decorators
 
///Terabyte Decorator
<?php
class Terabyte extends Decorator 
{
	private  $computer;
	public function __construct(IComponent $computer) 
	{
		$this->computer = $computer;
	}
	public function getComputer() 
	{
		return $this->computer->getComputer() . "<br/>&nbsp;&nbsp; Terabyte ";
	}
	public function price() 
	{
		return 129 + $this->computer->price();
	}
}
?>
///Hub Decorator
<?php
class Hub extends IComponent 
{
	private  $computer;
	public function __construct(IComponent $computer) 
	{
		$this->computer = $computer;
	}
	public function getComputer() 
	{
		return $this->computer->getComputer() . "<br/>&nbsp;&nbsp; USB Hub ";
	}
	public function price() 
	{
		return 82 + $this->computer->price();
	}
}
?>
 
///Big Monitor Decorator
<?php
<?php
class BigMonitor extends IComponent 
{
	private  $computer;
	public function __construct(IComponent $computer) 
	{
		$this->computer = $computer;
	}
	public function getComputer() 
	{
		return $this->computer->getComputer() . "<br/>&nbsp;&nbsp; Big Monitor ";
	}
	public function price() 
	{
		return 325 + $this->computer->price();
	}
}
?>

The best features of the concrete decorators is the ability to change the price or description without disrupting the rest of the program. Adding more concrete decorations is just as easy—all without having to re-write large portions of the program.

The Client

The client class makes its request by first determining which component the user has selected—an “apple” or “dell.” Then, if an accessory has been requested, it then wraps the component in one or more decorators. At most, the Client deals with a single component and three decorators. Each value is added to the price—the component’s base price and the each decorator’s price as it is added.

<?php
ERROR_REPORTING( E_ALL | E_STRICT );
ini_set("display_errors", 1);
function __autoload($class_name) 
{
    include $class_name . '.php';
}
 
class Client
{
	private $computerNow;
	private $accessory;
	private $n;
 
	public function __construct()
	{	
		$computer=$_POST['query'];
 
		if($computer=="apple")
		{
			$this->computerNow = new Apple();
		}
		else
		{
			$this->computerNow = new Dell();
		}
 
	if(!empty($_POST['accessories']))
		{
			$this->accessory=$_POST['accessories'];
			$this->n=count($this->accessory);
			$this->accessorize();
		}
 
		print $this->computerNow->getComputer() . "<br/>&nbsp;&nbsp;<strong>Total= $" . $this->computerNow->price() . "</strong><p/>";	
	}
 
	private function accessorize()
	{
		for($a=0; $a < $this->n; $a++)
		{
			switch($this->accessory[$a])
			{
				case "bigmonitor":
					$this->computerNow =new BigMonitor($this->computerNow);
					break;
				case "terabyte":
					$this->computerNow =new Terabyte($this->computerNow);
					break;
				case "hub":
					$this->computerNow =new Hub($this->computerNow);					
			}
		} 
	}
}
$goClient=new Client()
?>

The operations within the private function to add the accessories could have been done within the constructor function. Placing it off on its own seemed like one way to distinguish the decorators from the components and perhaps easier to have update with added accessories.

The User Interface

The UI is an HTML program that calls the Client (php) class and passes what the user enters in radio buttons and check boxes. This one is very simple and does not even have such elemental validation. First, nothing beyond the basics (the structure) of the Decorator design pattern is revealed making it easier to see how to use the Decorator. Second, you can easily fix it up to your own style and taste—after all, it’s just HTML.

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="decorator.css">
<meta charset="UTF-8">
<title>Choose Computer</title>
</head>
<body>
<h2>Decorator Computer Store</h2>
<h3>Select a Compuer:</h3>
<form action="Client.php" method="post" target="feedback">
  <input type="radio" name="query" value="dell"  />
  Dell Computer<br />
  <input type="radio" name="query" value="apple"  />
  Apple Macintosh
  <p />
  <h3>Select Components</h3>
  <input type="checkbox" name=accessories[] value="bigmonitor"  />
  Big Monitor<br />
  <input type="checkbox" name=accessories[] value="terabyte"  />
  Terabyte External Storage<br />
  <input type="checkbox" name=accessories[] value="hub"  />
  USB Hub
  <p />
  <input type="submit" name="sendNow" value="Place Order"  />
</form>
<iframe name="feedback" width="200" height="150" seamless>PHP</iframe>
</body>
</html>

Here’s the CSS:

@charset "UTF-8";
/* CSS Document */
 
body
{
	font-family:Verdana, Arial, Helvetica, sans-serif;
	color:#b9121b;
	background-color:#fcfae1;
	font-size;11pt;
}
h2	
{
	color:#f6e497;
        background-color: #4c1b1b;
        width: 400px;
	padding-left: 1em;
}
h3
{
	color:#b9121b;
        background-color: #f6e497;
	width: 240px;
	padding-left: 1em;
}
iframe
{
	background-color: #f6e497;
}

If you add new components or concrete decorations, all you need to do is add the corresponding input tags in the HTML.

Putting the Decorator to Work

This example of the Decorator is best used in terms making changes. Adding decorators is easy, and they are only used when needed. Likewise, adding components is simply a matter of creating a new concrete component class with the base description and price.

You may be thinking that this looks just like the Strategy pattern. However, there are important differences. The decorator only changes the component from the outside. Thus, the component doesn’t have to know anything about the decorator. However, in the Strategy pattern the component knows about the possible extensions (algorithms) and must maintain a reference to them.

You can explore the Decorator from several different perspectives and try it out with applications where you need to have different component features without having to subclass them. I think that you can appreciate the flexibility of the Decorator most when it comes time to add new features to fundamental components without the need to change them.

Share

Copyright © 2010 William Sanders. All Rights Reserved.

8 Responses to “PHP Decorator Design Pattern: Accessorizing Your Classes”


  • Hi,
    I’ve enjoyed reading your article. Please forgive me if I’m wrong, I’m not an experienced programmer but wouldn’t the line in the client constructor: print $this->computerNow->getComputer() return null for a new Dell() object?
    Shane

  • Looks like maybe a bad copy paste on the concrete component classes. It would seem that the first example is setting the description member variable correctly but fails to implement IComponent by not having a getComputer method. It does have a getDescription method.

    The second example has a correct getComputer method but is setting the computer member variable to what should be it’s description.

  • And by the way, awesome blog.

  • @Shane,

    Sorry man! It has taken me 2 years to respond, but somehow I missed the comments on this post. If you select Dell Computer in the HTML ‘Decorator Computer Store” what do you get? Click the Play button, and you will see that if you select no ‘decorations’, you get Dell Computer $599. The Apple and Dell computers are the basic the two Concrete Components. They demonstrate object. If you want to add things to the objects without having to change them, you ‘decorate’ them.

    Kindest regards,
    Bill

  • @Shane, @Bill

    After a quick glance over the code for the Dell concrete component i noticed that the Dell component stores the description in the private $description, while the Apple component stores it in the private $computer.

    Thus, when you call the getComputer function it will return a null.

    (also, the getComputer function is not defined in the Dell component code but i’m not sure if that’s part of the problem.)

  • Hi Everyone,

    It looks like I put some wrong files in the write-up. Everything is working dandy in the sample but there’s some crazy stuff in the post. That’s all on me, and I’ll fix it. Just to be on the safe side, I’m putting in a new download file too.

    Thanks Abba, Wilco & Shane! It’ll be up in a jiffy!

    Kindest regards,
    Bill

  • If you want to filter results, when you have a dynamic query, like selecting products, color, size,…etc. will be this pattern the first choice? I am asking because some people will recommend chain of responsibility. Great website!

  • Hi George,

    Good question….actually, great question! I use a Chain of Responsibility (CoR) when there is only a single result that the user wants to see. For example, a Help Desk app has single answers (or multiple answers in one category) and so a query goes through several different choices until the answer or category of answers is reached. Then it stops and presents the results.

    However, with a Decorator, you may want several different features, and so instead of selecting only one, the Decorator may add several.

    Earlier today I was working on a CoR with a Factory Method. The CoR responded to a week (PHP date object used), and depending on which week is the current one, it had different text and graphics presented on a page that the Factory Method generated for it.

    If you have further inquiries about this; just let me know.

    Kindest regards,
    Bill Sanders

Leave a Reply