What is design pattern
In software engineering, a software design pattern is a general reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
Decorator pattern
Changes behavior, adds functionality to an individual objects, usually at runtime (dynamically).This means that it doesn’t affect other objects of that class. Also, usually it doesn’t change the interface of the object. Relationship between the Decorator class and class which it decorates is both HAS_A and IS_A (or if not IS_A, at least it implements the same interface – point being that decorator can be drop in replacement for the “original” object).
Example
Description
Lets decorate an existing class which sends some text over the network, to the log file, or just prints on the screen – not important for this tutorial. Functionality like sending compressed text, obfuscate text, etc… will be implemented by using Decorator pattern.
Class which we will decorate is MessageWritter and it implements interface MessageWritterInterface. Put them into message subdirectory.
message/messagewritterinterface.php
1 2 3 4 5 6 7 8 |
<?php namespace message; interface MessageWriterInterface { public function writeText($text); } |
message/messagewritter.php
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php namespace message; class MessageWriter implements MessageWriterInterface { public function writeText($text) { // for the test - just print it to the screen print $text; } } |
Example of MessageWriter usage:
main.php
1 2 3 4 5 6 7 8 9 10 |
<?php spl_autoload_register(); $str='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula.'; echo "MessageWriter:\n"; $writer1 = new Message\MessageWriter(); $writer1->writeText($str); echo "\n"; |
Save main.php outside of the message subdirectory.
Output:
1 2 3 |
[damir@buffy decorator]$ php -q main.php MessageWriter: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula. |
Nothing spectacular for now. Lets write the first decorator:
message/gzcompressmessagewriterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php namespace message; class GzCompressMessageWriterDecorator implements MessageWriterInterface { protected $_messageWriter=null; public function __construct(MessageWriterInterface $messageWriter) { $this->_messageWriter = $messageWriter; } public function writeText($text) { $text = gzcompress($text); $this->_messageWriter->writeText($text); } } |
Please notice a HAS_A relationship since the $_messageWriter is a member variable which contains object which implements MessageWriterInterface. Decorator also implements MessageWriterInterface – this is kind of IS_A relationship since decorator can be drop in replacement anywhere where MessageWriterInterface is expected.
Decorator implementation of the writeText compresses the text before it is outputted by the “original” object.
Add the following lines to the main.php:
1 2 3 4 |
echo "GzCompressMessageWriterDecorator - MessageWriter\n"; $writer2 = new Message\GzCompressMessageWriterDecorator( new Message\MessageWriter() ); $writer2->writeText($str); echo "\n"; |
Output:
1 2 3 4 5 6 |
[damir@buffy decorator]$ php -q main.php MessageWriter: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula. GzCompressMessageWriterDecorator - MessageWriter �0�� �'>)W��QyTl~c����2A4��"�-a��y'ޥ�%饉a�yJ���X��_���G�<] |
So here it is: functionality to output (or send compressed) text was added at the runtime.
But now, we might get into problems, what if compressed text is to be transported over text only media? Lets write base64 decorator:
message/base64messagewriterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?php namespace message; class Base64MessageWriterDecorator implements MessageWriterInterface { protected $_messageWriter=null; public function __construct(MessageWriterInterface $messageWriter) { $this->_messageWriter = $messageWriter; } public function writeText($text) { $text = base64_encode($text); $this->_messageWriter->writeText($text); } } |
Please remember that Decorator wraps around any object that implements MessageWriterInterfaceso it can wrap around the original object MessageWritter or any MessageWritter decorators. We can chain decorators!
Add the following lines to the main.php:
1 2 3 4 |
echo "GzCompressMessageWriterDecorator - Base64MessageWriterDecorator - MessageWriter:\n"; $writer3 = new Message\GzCompressMessageWriterDecorator( new Message\Base64MessageWriterDecorator(new Message\MessageWriter())); $writer3->writeText($str); echo "\n"; |
Output:
1 2 3 4 5 6 7 8 |
[damir@buffy decorator]$ php -q main.php MessageWriter: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula. GzCompressMessageWriterDecorator - MessageWriter: �0�� �'>)W��QyTl~c����2A4��"�-a��y'ޥ�%饉a�yJ���X��_���G�<] GzCompressMessageWriterDecorator - Base64MessageWriterDecorator - MessageWriter: eJwVzMkNwzAMRNFWpgDDlQQ55i5ThENAIg0uqT/Sed6flzlPyBM10W2YIyTRJucBMg2m5CxH6/JIkOgNHpInPhwpV43VUXlUbH4XY6yk4oAyQTTZ+yLsLWGuzXkPJ96lhBQl6aWJYZd5SqDV/lj4x1+hGu38A0ezPF0= |
In the example above text is first compressed (since GzCompressMessageWriterDecorator::writeText() ) is executed first. Then it is converted to the base64 (since Base64MessageWriterDecorator::writeText() is executed second) and finally the text is outputted by MessageWritter::writeText() .
Technically, we can chain decorators in any order, but we will not get the desired result if we order them in a wrong way. For example this:
1 |
$writer3 = new Message\Base64MessageWriterDecorator( new Message\GzCompressMessageWriterDecorator(new Message\MessageWriter())); |
would first convert text to base64 and then compress it – but there is no point of doing this.
Please note another advantage – since decorators can be chained – we can have decorators that do one thing and then combined them in any way we wish. From example above: it is enough that we have gzcompress decorator and base64 decorator, we don’t need a third decorator which will implement gzcompress and base64.
Refactoring
There is fragment of the code which appears in both decorators:
1 2 3 4 5 6 |
protected $_messageWriter=null; public function __construct(MessageWriterInterface $messageWriter) { $this->_messageWriter = $messageWriter; } |
This code can be put into MessageWriterDecorator class, which will be extended by other decorator classes, so there will be no code duplication:
message/messagewritterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?php namespace message; class MessageWriterDecorator implements MessageWriterInterface { protected $_messageWriter; public function __construct(MessageWriterInterface $messageWriter) { $this->_messageWriter = $messageWriter; } public function writeText($text) { $this->_messageWriter->writeText($text); } } |
Update the Base64MessageWriterDecorator and GzCompressMessageWriterDecorator to inherit from MessageWriterDecorator. This is their updated version:
message/gzcompressmessagewriterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php namespace message; class GzCompressMessageWriterDecorator extends MessageWriterDecorator { public function writeText($text) { $text = gzcompress($text); $this->_messageWriter->writeText($text); } } |
message/base64messagewriterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 |
<?php namespace message; class Base64MessageWriterDecorator extends MessageWriterDecorator { public function writeText($text) { $text = base64_encode($text); $this->_messageWriter->writeText($text); } } |
Just for fun here is the third l33t decorator:
message/leetmessagewriterdecorator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace message; class LeetMessageWriterDecorator extends MessageWriterDecorator { public function writeText($text) { $text = strtolower($text); $text = strtr($text, "oesaig", "035416"); $this->_messageWriter->writeText($text); } } |
Add the following lines to the main.php:
1 2 3 4 |
echo "LeetMessageWriterDecorator - MessageWriter:\n"; $writer4 = new Message\LeetMessageWriterDecorator( new Message\MessageWriter()); $writer4->writeText($str); echo "\n"; |
Output:
1 2 3 4 5 6 7 8 9 10 |
[damir@buffy decorator]$ php -q main.php MessageWriter: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula. GzCompressMessageWriterDecorator - MessageWriter: �0�� �'>)W��QyTl~c����2A4��"�-a��y'ޥ�%饉a�yJ���X��_���G�<] GzCompressMessageWriterDecorator - Base64MessageWriterDecorator - MessageWriter: eJwVzMkNwzAMRNFWpgDDlQQ55i5ThENAIg0uqT/Sed6flzlPyBM10W2YIyTRJucBMg2m5CxH6/JIkOgNHpInPhwpV43VUXlUbH4XY6yk4oAyQTTZ+yLsLWGuzXkPJ96lhBQl6aWJYZd5SqDV/lj4x1+hGu38A0ezPF0= LeetMessageWriterDecorator - MessageWriter: l0r3m 1p5um d0l0r 51t 4m3t, c0n53ct3tur 4d1p15c1n6 3l1t. v35t1bulum cur5u5 c0n6u3 l3ctu5, n3c 1nt3rdum 3r4t 0rn4r3 n3c. nunc t1nc1dunt l0b0rt15 4u6u3 4t v3h1cul4. |
Add the following lines to the main.php:
1 2 3 4 |
echo "Base64MessageWriterDecorator - LeetMessageWriterDecorator - MessageWriter:\n"; $writer5 = new Message\Base64MessageWriterDecorator(new Message\LeetMessageWriterDecorator( new Message\MessageWriter())); $writer5->writeText($str); echo "\n"; |
Base64 l33t output – because – why not? 😉
1 2 3 4 5 6 7 8 9 10 11 12 |
[damir@buffy decorator]$ php -q main.php MessageWriter: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula. GzCompressMessageWriterDecorator - MessageWriter: �0�� �'>)W��QyTl~c����2A4��"�-a��y'ޥ�%饉a�yJ���X��_���G�<] GzCompressMessageWriterDecorator - Base64MessageWriterDecorator - MessageWriter eJwVzMkNwzAMRNFWpgDDlQQ55i5ThENAIg0uqT/Sed6flzlPyBM10W2YIyTRJucBMg2m5CxH6/JIkOgNHpInPhwpV43VUXlUbH4XY6yk4oAyQTTZ+yLsLWGuzXkPJ96lhBQl6aWJYZd5SqDV/lj4x1+hGu38A0ezPF0= LeetMessageWriterDecorator - MessageWriter: l0r3m 1p5um d0l0r 51t 4m3t, c0n53ct3tur 4d1p15c1n6 3l1t. v35t1bulum cur5u5 c0n6u3 l3ctu5, n3c 1nt3rdum 3r4t 0rn4r3 n3c. nunc t1nc1dunt l0b0rt15 4u6u3 4t v3h1cul4. Base64MessageWriterDecorator - LeetMessageWriterDecorator - MessageWriter: t69yzw064xbzdw06z695b316c2l016ftzxq516nvbnnly3rldhvy16fk4xbpc2npbmc6zwxpdc46vmvzd6l1dwx1b5bjdxjzdxm6y29uz3vl16xly3r1cyw6bmvj16lud6vyzhvt16vyyxq6b3juyxjl165lyy46tnvuyyb04w5j4wr1bnq6b691b3j04xm6yxvndwu6yxq6dmv04wn1b63u |
Complete main.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?php spl_autoload_register(); $str='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum cursus congue lectus, nec interdum erat ornare nec. Nunc tincidunt lobortis augue at vehicula.'; echo "MessageWriter:\n"; $writer1 = new Message\MessageWriter(); $writer1->writeText($str); echo "\n"; echo "GzCompressMessageWriterDecorator - MessageWriter:\n"; $writer2 = new Message\GzCompressMessageWriterDecorator( new Message\MessageWriter() ); $writer2->writeText($str); echo "\n"; echo "GzCompressMessageWriterDecorator - Base64MessageWriterDecorator - MessageWriter\n"; $writer3 = new Message\GzCompressMessageWriterDecorator( new Message\Base64MessageWriterDecorator(new Message\MessageWriter())); $writer3->writeText($str); echo "\n"; echo "LeetMessageWriterDecorator - MessageWriter:\n"; $writer4 = new Message\LeetMessageWriterDecorator( new Message\MessageWriter()); $writer4->writeText($str); echo "\n"; echo "Base64MessageWriterDecorator - LeetMessageWriterDecorator - MessageWriter:\n"; $writer5 = new Message\Base64MessageWriterDecorator(new Message\LeetMessageWriterDecorator( new Message\MessageWriter())); $writer5->writeText($str); echo "\n"; |
Issues and alternatives
If you are decorating object with lot of public methods you are forced to delegate all of them to the decorated object. In other words, you need to write wrapper methods for each public method which will only call decorated object method. Your decorator will be full of code like this:
1 2 3 4 5 6 7 8 9 |
public function foo() { return $this->_decoratedObj->foo(); } public function bar() { return $this->_decoratedObj->bar(); } |
etc… Which is just too much useless work. Unfortunately you can’t use PHP magic methods like __call since you are implementing interface and all methods must be implemented.
Alternatively you can make decorator not be associated with the decorated class in any way: it doesn’t implement the same interface, it doesn’t extend the same abstract class, etc… . In that case you can use magic methods for methods and properties which you don’t want to decorate. Since PHP is loosely typed language this will work. But you will loose the possibility to use type declarations (previously known as type hinting) – which can be very helpful in the larger projects.