Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

full inheritance behaviour with Decorator in php

I'm not really used to design pattern generally, and I never used Decorator. I want an object which can have different behaviour according to the context. These behaviours are defined in different classes. I guess Decorator does the trick. But I need that each decorator can access to the same properties, and call children methods first, like with inheritance. So here what I've done:

abstract class Component{

    /**
     * Used to access last chain Decorator
     *
     * @var Decorator
     */
    protected $this;

    protected $prop1;//These properies have to be accessed in any decorators

    protected $prop2;

    protected $prop3;

    //this method is used to share properties with the childrens
    public function getAttributesReferencesArray() {
        $attributes=[];
        foreach($this as $attr=>&$val)
                $attributes[$attr]=&$val;
        return $attributes;
    }

}

class Foo extends Component{

    public function __construct() {
        $this->prop1="initialized";
        //...
    }

    public function method1() {//this method can be "overrided" and called here
        //...
    }

    public function method2() {//this method call the overrided or not method1
        //...
        $this->this->method1();
        //...
    }

}

abstract class Decorator extends Component{

    /**
     * Used to access parent component
     *
     * @var Component
     */
    protected $parent;

    public function __construct(Component $parent) {
        $attributes=$parent->getAttributesReferencesArray();
        foreach($attributes as $attr=>&$val)
                $this->{$attr}=&$val;
        $this->parent=$parent;
        $this->this=$this;
    }

    public function __call($method, $args) {
        if(!$this->parent instanceof Decorator &&
            !method_exists($this->parent, $method))
                throw new Exception("Undefined method $method attempt.");
        return call_user_func_array(array($this->parent, $method), $args);
    }

}

class Bar extends Decorator{

    //this method call the component method (I guess Decorator classical way)
    public function method1(){
        //...
        $this->parent->method1();
        $this->prop2="set in Bar";
    }
}

class Baz extends Decorator{

    public function method2(){//this method call the overrided or not method1
        //...
        $this->this->method1();
        //...
    }

}

Now we can "construct" the "inheritance" according to the context:

//...
$obj=new Foo();
if($context->useBar())
        $obj=new Bar($obj);
if($context->somethingElse())
        $obj=new Baz($obj);

and run the object with abstraction of behaviour:

$obj->method1();
//...

It does what I want, but:

  • there isn't anymore encapsulation
  • $this->parent is ugly
  • $this->this is ugly

What do you think about that?

  • How can I access decorator ("children") method another way
  • How can I share properties like if they where protected in an inherited context
  • Is it a bad usage of Decorator?
  • Is there some more elegant pattern that does the trick
  • parent and this attributes are a kind of reinventing the wheel isn't it?

A real world example: the coffee machine

abstract class CoffeeFactory{// Component

    /**
     * Used to access last chain Decorator
     *
     * @var Decorator
     */
    protected $this;

    /**
     * Used to access user choices
     *
     * @var CoffeeMachine
     */
    protected $coffeeMachine;

    protected $water;//the water quantity in cl

    protected $coffeePowder;

    protected $isSpoon=FALSE;

    protected $cup=[];

    //this method is used to share properties with the childrens
    public function getAttributesReferencesArray() {
        $attributes=[];
        foreach($this as $attr=>&$val)
                $attributes[$attr]=&$val;
        return $attributes;
    }

}

class SimpleCoffeeFactory extends CoffeeFactory{//Foo

    public function __construct(CoffeeMachine $coffeeMachine) {
        $this->coffeeMachine=$coffeeMachine;
        $this->water=$coffeeMachine->isEspresso()?10:20;
        $this->coffeePowder=$coffeeMachine->isDouble()?2:1;
        $this->water-=$this->coffeePowder;
        $this->this=$this;
    }

    private function addCoffeePowder(){
        $this->cup["coffeePowder"]=$this->coffeePowder;
    }

    private function addSpoon(){
        if($this->isSpoon)
                $this->cup["spoon"]=1;
    }

    public function isWaterHot($boilingWater){
        return $this->getWaterTemperature($boilingWater)>90;
    }

    private function addWater() {
        $boilingWater=$this->getWaterForBoiling($this->water);
        while(!$this->this->isWaterHot($boilingWater))
                $this->boilWater($boilingWater);
        $this->cup["water"]=$boilingWater;
    }

    public function prepare() {
        $this->addCoffeePowder();
        $this->addSpoon();
    }

    public function getCup() {
        $this->this->prepare();
        $this->addWater();
        return $this->cup;
    }

}

abstract class Decorator extends CoffeeFactory{

    /**
     * Used to access parent component
     *
     * @var Component
     */
    protected $parent;

    public function __construct(Component $parent) {
        $attributes=$parent->getAttributesReferencesArray();
        foreach($attributes as $attr=>&$val)
                $this->{$attr}=&$val;
        $this->parent=$parent;
        $this->this=$this;
    }

    public function __call($method, $args) {
        if(!$this->parent instanceof Decorator &&
            !method_exists($this->parent, $method))
                throw new Exception("Undefined method $method attempt.");
        return call_user_func_array(array($this->parent, $method), $args);
    }
}

class SugarCoffeeFactory extends Decorator{

    protected $sugar;

    public function __construct(Component $parent) {
        parent::__construct($parent);
        $this->sugar=$this->coffeeMachine->howMuchSugar();
        $this->water-=$this->sugar;
        $this->isSpoon=TRUE;
    }

    public function prepare() {
        $this->cup['sugar']=$this->sugar;
        $this->parent->prepare();
    }
}

class MilkCoffeeFactory extends Decorator{

    protected $milk;

    public function __construct(Component $parent) {
        parent::__construct($parent);
        $this->milk=$this->coffeeMachine->howMuchMilk();
        $this->water-=$this->milk;
    }

    public function prepare() {
        $this->parent->prepare();
        $this->cup['milk']=$this->milk;
    }

    public function isWaterHot($boilingWater){
        //The milk is added cold, so the more milk we have, the hotter water have to be.
        return $this->getWaterTemperature($boilingWater)>90+$this->milk;
    }

}

//Now we can "construct" the "inheritance" according to the coffee machine:

//...
$coffeeFactory=new SimpleCoffeeFactory($coffeeMachine);
if($coffeeMachine->wantSugar())
        $coffeeFactory=new SugarCoffeeFactory($coffeeFactory);
if($coffeeMachine->wantMilk())
        $coffeeFactory=new MilkCoffeeFactory($coffeeFactory);

//and get our cup with abstraction of behaviour:

$cupOfCoffee=$coffeeFactory->getCup();
//...
like image 207
Pierre Avatar asked Sep 15 '25 17:09

Pierre


1 Answers

The Decorator pattern is not made to do internal changes in the base class (you call this one parent). What you are doing is bad usage of this pattern. The Decorators should only change the output of the functions instead of playing with variables.

One solution is to define getters and setters for your protected variables and to call them from the Decorator.

Another solution is what I prefer personally and that is splitting the behaviour which is dependent on the context and the base class:

class Component {
    protected $behaviour;
    function __construct() {
        $this->behaviour = new StandardBehaviour();
    }

    function method1() {
        $this->prop2 = $this->behaviour->getProp2Value();
    }
    function setBehaviour(Behaviour $behaviour) {
        $this->behaviour = $behaviour;
    }
}

abstract class Behaviour {
    abstract function getProp2Value();
}

class StandardBehaviour extends Behaviour {
    function getProp2Value() {
        return 'set by bahaviour ';
    }
}

class BarBehaviour extends StandardBehaviour {
    function getProp2Value() {
        return parent::getProp2Value().' Bar';
    }
}

class BazBehaviour extends BarBehaviour {
    function getProp2Value() {
        return 'set in Baz';
    }
}

Now we can use it like this:

$obj=new Foo();
if($context->useBar())
    $obj->setBehaviour(new BarBehaviour);
if($context->somethingElse())
    $obj->setBehaviour(new BazBehaviour);

I hope this answers your question!

EDIT after comments

I see your point that the behaviours replace each other instead of chaining. This is indeed a typical problem for the decorator class. However you really shouldn't change the original class in a decorator class. A decorator class only 'decorates' output of the original. Below a typical example of how the decorator pattern would be used in the real world scenario you mentioned:

interface ICoffeeFactory {
    public function produceCoffee();
}

class SimpleCoffeeFactory implements ICoffeeFactory{
    protected $water;//the water quantity in cl

    public function __construct() {
        $this->water=20;
    }

    protected function addCoffeePowder($cup){
        $cup["coffeePowder"]=1;
        return $cup;
    }

    protected function addWater($cup) {
        $cup["water"]=$this->water;
        return $cup;
    }

    public function produceCoffee() {
        $cup = array();
        $cup = $this->addCoffeePowder($cup);
        $cup = $this->addSpoon($cup);
        $cup = $this->addWater($cup);
        return $cup;
    }

}

class EspressoCoffeeFactory extends SimpleCoffeeFactory {
    public function __construct() {
        $this->water=5;
    }

    protected function addCoffeePowder($cup){
        $cup["coffeePowder"]=3;
        return $cup;
    }
}

abstract class Decorator implements ICoffeeFactory {
    function __construct(ICoffeeFactory $machine)
}

class SugarCoffee extends Decorator{
    public function produceCoffee() {
        $cup = $this->factory->produceCoffee();
        if ($cup['water'] > 0)
            $cup['water'] -= 1;

        $cup['spoon']  = TRUE;
        $cup['sugar'] += 1;
        return $cup;
    }
}

class MilkCoffee extends Decorator{
    protected function produceCoffee() {
        $cup = $this->factory->produceCoffee();
        $cup['milk'] = 5;
        return $cup;
    }
}

//Now we can "construct" the "inheritance" according to the coffee machine:

//...
$coffee=new SimpleCoffeeFactory();
if($coffeeMachine->wantSugar())
        $coffee=new SugarCoffee($coffee);
if($coffeeMachine->wantMilk())
        $coffee=new MilkCoffee($coffee);

//and get our cup with abstraction of behaviour:

$cupOfCoffee=$coffee->produceCoffee();
//...
like image 163
Mohamed Alkaduhimi Avatar answered Sep 17 '25 07:09

Mohamed Alkaduhimi