Let's understand it
Example 1
We've got a Person class that holds a surname:
class Person {
private $surname ;
public function __construct($surname)
{
$this->surname = $surname;
}
public function displayAllWeKnow()
{
return "surname "$this->surname;
}
}
$p =new Person("Verma");
echo $p->displayAllWeKnow(); // surname Verma
It works perfectly for surnames, but we need first names also as requirements grow.
Solution 1: The obvious solution is to add one more variable
class Person {
private $surname ;
private $firstname;
...
Sometimes, this is the right thing to do. But for particular complex required, it may have broader effects
If we are ok with these effects, then we can go with this solution
Solution 2: Subclass
class Person {
private $surname ;
public function __construct($surname)
{
$this->surname = $surname;
}
public function displayAllWeKnow()
{
return $this->surname;
}
}
class PersonWithName extends Person {
private $firstName ;
private $ps;
public function __construct($ps, $firstName)
{
$this->ps = $ps;
$this->firstName = $firstName;
}
public function displayAllWeKnow()
{
return "First Name ".$this->firstName. " surname ". $this->ps->displayAllWeKnow();
}
}
$ps = new Person("Verma");
$pf = new PersonWithName($ps,"Puneet");
echo $pf->displayAllWeKnow(); //First Name Puneet surname Verma
Again new requirement came. We need a middle name, also.
We can apply the same logic and have one more subclass inherited from PersonWithName. That will work. What if we require different combinations
For each combination, we can have a subclass for the required behaviour.
What if we have n number of required? Then will have a complex mesh of subclass, and impractical to do so.
Let's have one more example to understand this.
We have an application which sends notifications for important events.
It starts with e-mail notifications and then extends to SMS and slack notifications. Up to this, everything works smoothly.
At some point, you realize that users require several notification types at once, like SMS, Facebook or any possible combination.
You tried to address that problem by creating special subclasses which combined several notification methods within one class. However, it quickly became apparent that this approach would bloat the code immensely, not only the library code but also the client code.
So subclassing is not very useful, for such a problem.
Solution 3: Decorators
We have a special design for such situations.
<?php
interface Person
{
public function displayAllWeKnow();
}
class Surname implements Person
{
private $surname;
public function __construct()
{
$this->surname = "Verma";
}
public function displayAllWeKnow()
{
return $this->surname;
}
}
class FirstName implements Person {
private $person;
private $firstName;
public function __construct($person) // in php $person as type in not required, in some language we have declare like Person $person
{
$this->person = $person;
$this->firstName = "Puneet";
}
public function displayAllWeKnow()
{
return $this->person->displayAllWeKnow()." ".$this->firstName;
}
}
class MiddleName implements Person {
private $person;
private $middleName;
public function __construct($person) // in php $person as type in not required, in some language we have declare like Person $person
{
$this->person = $person;
$this->middleName = "Kumar";
}
public function displayAllWeKnow()
{
return $this->person->displayAllWeKnow()." ".$this->middleName;
}
}
$surname = new Surname();
$surnameFirstName = new FirstName($surname);
$surnameMiddleName = new MiddleName($surname);
$SurnameFirstMiddle = new MiddleName($surnameFirstName);
$SurnameMiddleFirst = new FirstName($surnameMiddleName);
echo $surname->displayAllWeKnow(); //Verma
echo $surnameFirstName->displayAllWeKnow(); //Verma Puneet
echo $surnameMiddleName->displayAllWeKnow(); //Verma Kumar
echo $SurnameFirstMiddle->displayAllWeKnow(); //Verma Puneet Kumar
echo $SurnameMiddleFirst->displayAllWeKnow(); //Verma Kumar Puneet
Note Ordering affects the output.
echo $SurnameFirstMiddle->displayAllWeKnow(); //Verma Puneet Kumar
echo $SurnameMiddleFirst->displayAllWeKnow(); //Verma Kumar Puneet
Decorator
Real-world Analogy
Pros
Cons
Let's explore one more variation of solution 3. you may observe the same constructor we declare multiple times. To avoid this, we can have an abstract class.
interface Person
{
public function displayAllWeKnow();
}
class Surname implements Person
{
private $surname;
public function __construct()
{
$this->surname = "Verma";
}
public function displayAllWeKnow()
{
return $this->surname;
}
}
abstract class Name implements Person{
protected $person;
public function __construct($person) // in php $person as type in not required, in some language we have declare like Person $person
{
$this->person = $person;
}
}
class FirstName extends Name {
private $firstName ="Puneet";
public function displayAllWeKnow()
{
return $this->person->displayAllWeKnow()." ".$this->firstName;
}
}
class MiddleName extends Name {
private $middleName = "Kumar";
public function displayAllWeKnow()
{
return $this->person->displayAllWeKnow()." ".$this->middleName;
}
}
$surname = new Surname();
$surnameFirstName = new FirstName($surname);
$surnameMiddleName = new MiddleName($surname);
$SurnameFirstMiddle = new MiddleName($surnameFirstName);
$SurnameMiddleFirst = new FirstName($surnameMiddleName);
echo $surname->displayAllWeKnow(); //Verma
echo $surnameFirstName->displayAllWeKnow(); //Verma Puneet
echo $surnameMiddleName->displayAllWeKnow(); //Verma Kumar
echo $SurnameFirstMiddle->displayAllWeKnow(); //Verma Puneet Kumar
echo $SurnameMiddleFirst->displayAllWeKnow(); //Verma Kumar Puneet
Example 2
interface Car
{
function cost();
function description();
}
class Suv implements Car{
function cost()
{
return 30000;
}
function description()
{
return "Suv";
}
}
abstract class CarFeature implements Car{
protected $car;
public function __construct(Car $car)
{
$this->car = $car;
}
}
class SunRoof extends CarFeature{
function cost()
{
return $this->car->cost() +1500;
}
function description()
{
return $this->car->description().", sunroof";
}
}
class HighEndWheels extends CarFeature{
function cost()
{
return $this->car->cost() + 2000;
}
function description()
{
return $this->car->description().", high end wheels";
}
}
$basicCar = new Suv();
echo $basicCar->description(); //Suv
echo "\n";
$carWithSunRoof = new SunRoof($basicCar);
echo $carWithSunRoof->description(); //Suv, sunroof
echo "\n";
$carWithHighEndWheels = new HighEndWheels($basicCar);
echo $carWithHighEndWheels -> description(); //Suv, high end wheels
echo "\n";
$carWithSunRoofAndHighEndWheels = new HighEndWheels($carWithSunRoof);
echo $carWithSunRoofAndHighEndWheels -> description(); //Suv, sunroof, high end wheels
echo "\n";
$carWithHighEndWheelsAndSunRoof = new SunRoof($carWithHighEndWheels);
echo $carWithHighEndWheelsAndSunRoof -> description(); //Suv, high end wheels, sunroof
echo "\n";
Example 3:
<?php
interface FoodItem
{
public function cost();
}
// all we need is a burger
class Burger implements FoodItem
{
public function cost ()
{
return 4;
}
}
// Now decorate with cheese
class Cheese implements FoodItem
{
private $item;
public function __construct (FoodItem $item)
{
$this->item = $item;
}
public function cost ()
{
return $this->item->cost() + 0.25;
}
}
// now decorate with patty
class Patty implements FoodItem
{
private $item;
public function __construct (FoodItem $item)
{
$this->item = $item;
}
public function cost ()
{
return $this->item->cost() + 1;
}
}
$b = new Burger();
$c = new Cheese($b); // passing burger
$pb = new Patty($b); // passing burger
$pc = new Patty($c); // passing cheese burger$b->cost(); // 4
$c->cost(); // 4.25
$pb->cost(); // 5
$pc->cost(); // 5.25
Implementation
Usages
A few Java classes from the java.io package, like BufferedInputStream and LineNumberInputStream, use decorator design patterns.