Decorator pattern in PHP

The decorator pattern is another structural pattern. The aim of the decorator pattern is to extend an existing classes functionality without modifying existing classes and rather using composition to wrap and extend the existing functionality.

Using Decorators, existing behaviours will stay the same, but you will have additional behaviour extensions which can be used to modify the output.

What is the decorator pattern

You may think this sounds quite similar to the adapter pattern. They are both similar in that they both use a wrapper to modify the end result. But as discussed in my article on the adapter pattern. Adapters should be used for allowing incompatible interfaces to work together. The decorator pattern is used to extend existing functionality.

When to use the decorator pattern

So we know that we should use the decorator pattern when we want to extend functionality, but what the hell does that mean. The whole principle of the decorator pattern is to provide a means of extending the output of a component in multiple ways using any combination of decorator classes. Just like in the real world, where you have a bedroom, you may have multiple ways to decorate your room. For example these could be lamps, posters, pictures, curtains etc. It would still be possible to create an empty bedroom, but you should also be able to decorate the room with as many of the available decorators as you like.

How to use the decorator pattern

  1. The first thing you want to do is create an abstract class for your component (the bedroom) and then the component itself to provide the default behaviour.
  2. Next you want to create an abstract decorator which implements the components interface, this allow you to pass around and the component and decorated components interchangeably.
  3. The abstract decorator should have a constructor which is passed an instance of the component. If the component has already be wrapped by a decorator this could be passed instead. This means your component can be wrapped by a decorator and you then your decorated component can then be wrapped by another decorator. Rinse, repeat.
  4. Create a collection of concrete decorators (posters, lamps etc) which all implement your abstract decorator.
  5. Each concrete decorator should make a call to its encapsulated component or decorated component. This allows multiple decorators to be able to wrap each other.

Rules

  1. The abstract decorator should extend the components abstract class, this allows your component and decorators to work interchangeably within other decorators.
  2. Abstract decorators can provide some default values for the concrete decorators. I’ll give an example of this in the example section below.
  3. All decorators should implement your abstract decorator.

Advantages

  1. You are able to extend the functionality of your application without modifying existing interfaces. So you comply to the interface segregation principle.
  2. You can have any number of abstract decorators extend other abstract decorators. Allowing you to create a tree like structure of decorators.
  3. Decorators are a nice flexible alternative to subclassing.

Drawbacks

  1. Decorators usually result in lots of tiny classes. Over use of decorators can become confusing.
  2. Class initialisation is more complicated than usual, because you have to initialise the component and then wrap the decorators around it.
  3. Creating a large decorator tree is highly confusing. I recommend to never have a hierarchy of more than 2 decorators. 3 is painful, 4+ is insane.

Example

interface RoomInterface
{
    public function getContents();
}

class Bedroom implements RoomInterface
{
    public function getContents()
    {
        return "So you have a bedroom.\n";
    }
}

class Bathroom implements RoomInterface
{
    public function getContents()
    {
        return "So you have a bathroom.\n";
    }
}

abstract class RoomDecoratorAbstract implements RoomInterface
{
    protected $room;
    private   $contentText;

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

    protected abstract function styleContentText($text);
}

abstract class BedroomDecoratorAbstract extends RoomDecoratorAbstract
{
    protected function styleContentText($text)
    {
        return " - " . $text . "\n";
    }
}

abstract class BathroomDecoratorAbstract extends RoomDecoratorAbstract
{
    protected function styleContentText($text)
    {
        return " # " . $text . "\n";
    }
}

class BedroomPosterDecorator extends BedroomDecoratorAbstract
{
    private $contentText = "There is a mudder fudging poster in it.";
    
    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

class BedroomLampDecorator extends BedroomDecoratorAbstract
{
    private $contentText = "One does not simply have a lamp, without it containing artificial lava.";

    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

class BedroomBedDecorator extends BedroomDecoratorAbstract
{
    private $contentText = "You have a bed with a 40,000 spring mattress, 1,000 tog duvet and pillows made of bacon.";

    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

class BathroomBogDecorator extends BathroomDecoratorAbstract
{
    private $contentText = "There is a bog to do the business.";
    
    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

class BathroomShowerDecorator extends BathroomDecoratorAbstract
{
    private $contentText = "Look!! A brand new Aqualisa Quartz digital shower. Oooooooo.";

    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

class BathroomBasinDecorator extends BathroomDecoratorAbstract
{
    private $contentText = "Diamond taps and it even comes with a plug. Bang for your buck";

    public function getContents()
    {
        return $this->room->getContents() . $this->styleContentText($this->contentText);
    }
}

$bedroom         = new Bedroom();
$posterDecorator = new BedroomPosterDecorator($bedroom);
$lampDecorator   = new BedroomLampDecorator($posterDecorator);
$bedDecorator    = new BedroomBedDecorator($lampDecorator);

// So you have a bedroom.
echo $bedroom->getContents();

// So you have a bedroom.
// - There is a mudder fudging poster in it.
echo $posterDecorator->getContents();

// So you have a bedroom.
// - There is a mudder fudging poster in it.
// - One does not simply have a lamp, without it containing artificial lava.
echo $lampDecorator->getContents();

// So you have a bedroom.
// - There is a mudder fudging poster in it.
// - One does not simply have a lamp, without it containing artificial lava.
// - You have a bed with a 40,000 spring mattress, 1,000 tog duvet and pillows made of bacon.
echo $bedDecorator->getContents();

$bathroom        = new Bathroom();
$bogDecorator    = new BathroomBogDecorator($bathroom);
$showerDecorator = new BathroomShowerDecorator($bogDecorator);
$basinDecorator  = new BathroomBasinDecorator($showerDecorator);

// So you have a bathroom.
echo $bathroom->getContents();

// So you have a bathroom.
// # There is a bog to do the business.
echo $bogDecorator->getContents();

// So you have a bathroom.
// # There is a bog to do the business.
// # Look!! A brand new Aqualisa Quartz digital shower. Oooooooo.
echo $showerDecorator->getContents();

// So you have a bathroom.
// # There is a bog to do the business.
// # Look!! A brand new Aqualisa Quartz digital shower. Oooooooo.
// # Diamond taps and it even comes with a plug. Bang for your buck
echo $basinDecorator->getContents();
15 Love This

Leave a Reply

Your email address will not be published.