The book says:
The decorator pattern can be used to extend (decorate) the
functionality of a certain object
I have a rabbit animal. And I want my rabbit to have, for example, reptile skin. Just want to decorate a common rabbit with reptile skin.
I have the code. First I have abstract class Animal with everythig that is common to any animal:
abstract class Animal {
abstract public function setSleep($hours);
abstract public function setEat($food);
abstract public function getSkinType();
/* and more methods which for sure will be implemented in any concrete animal */
}
I create class for my rabbit:
class Rabbit extends Animal {
private $rest;
private $stomach;
private $skinType = "hair";
public function setSleep($hours) {
$this->rest = $hours;
}
public function setFood($food) {
$this->stomach = $food;
}
public function getSkinType() {
return $this->$skinType;
}
}
Up to now everything is OK. Then I create abstract AnimalDecorator class which extends Animal:
abstract class AnimalDecorator extends Animal {
protected $animal;
public function __construct(Animal $animal) {
$this->animal = $animal;
}
}
And here the problem comes. Pay attention that AnimalDecorator also gets all the abstract methods from the Animal class (in this example just two but in real can have many more).
Then I create concrete ReptileSkinDecorator class which extends AnimalDecorator. It also has those the same two abstract methods from Animal:
class ReptileSkinDecorator extends AnimalDecorator {
public function getSkinColor() {
$skin = $this->animal->getSkinType();
$skin = "reptile";
return $skin;
}
}
And finaly I want to decorate my rabbit with reptile skin:
$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());
But I can’t do this because I have two abstract methods in ReptileSkinDecorator class. They are:
abstract public function setSleep($hours);
abstract public function setEat($food);
So, instead of just re-decorating only skin I also have to re-decorate setSleep() and setEat(); methods. But I don’t need to.
In all the book examples there is always ONLY ONE abstract method in Animal class. And of course it works then. But here I just made very simple real life example and tried to use the Decorator pattern and it doesn’t work without implementing those abstract methods in ReptileSkinDecorator class.
It means that if I want to use my example I have to create a brand new rabbit and implement for it its own setSleep() and setEat() methods. OK, let it be. But then this brand new rabbit has the instance of commont Rabbit I passed to ReptileSkinDecorator:
$reptileSkinRabbit = ReptileSkinDecorator(new Rabbit());
I have one common rabbit instance with its own methods in the reptileSkinRabbit instance which in its turn has its own reptileSkinRabbit methods. I have rabbit in rabbit. But I think I don’t have to have such possibility.
I don’t understand the Decarator pattern right way. Kindly ask you to point on any mistakes in my example, in my understanding of this pattern.
Thank you.
Sounds like you’re trying to force the use of a particular pattern that doesnt fit the problem. A decorator usually aggregates something additional (add braids to the hair), instead of completely changing it, like you’re trying to do with the skin (hair to scales).
A Builder pattern, (whereby you specify how you want the object built) may fit the problem better. In your case, you want to build a rabbit built with a reptile skin. (where Im from, instead of reptile skin, it would have been funny to make a rabbit with horns and call it a jackalope 🙂
I think the use of a Builder (or any pattern) here may actually be overkill. Do you absolutely have to use a pattern for this particular problem? How about just defining the code as follows and leave patterns out of it for now:
Disclaimer: Im a c++ guy and my php is really bad, Im sure this code has several problems, but I hope you get the idea.
So a Rabbit would just extend an Animal and set its properties accordingly. Then if somebody wanted a RabbitReptile, they could either extend Rabbit (probably overkill) or just make a Rabbit and set the skin type accordingly.