php中的Decorator完全继承行为

时间:2013-08-07 21:27:47

标签: php oop inheritance decorator decorator-chaining

我一般不习惯设计模式,我从未使用过装饰器。我想要一个根据上下文可以有不同行为的对象。这些行为在不同的类中定义。我猜装饰师会做到这一点。但我需要每个装饰器都可以访问相同的属性,并首先调用children方法,就像继承一样。所以我在这里做了:

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();
        //...
    }

}

现在我们可以根据上下文“构建”“继承”:

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

并使用行为抽象运行对象:

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

它做我想要的,但是:

  • 没有任何封装
  • $ this-> parent is ugly
  • $ this->这很丑陋

你怎么看?

  • 如何以其他方式访问decorator(“children”)方法
  • 如何共享属性,例如在继承的上下文中受保护的属性
  • 装饰器的用法不好吗?
  • 是否有更优雅的模式可以解决这个问题
  • 父和这个属性是一种重塑轮子不是吗?

一个真实世界的例子:咖啡机

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();
//...

3 个答案:

答案 0 :(得分:2)

装饰器模式不是为了在基类中进行内部更改(您将其称为父类)。你正在做的是这种模式的糟糕用法。装饰器应该只改变函数的输出而不是使用变量。

一种解决方案是为受保护的变量定义getter和setter,并从Decorator中调用它们。

另一种解决方案是我个人喜欢的解决方案,即拆分依赖于上下文和基类的行为:

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';
    }
}

现在我们可以像这样使用它:

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

我希望这能回答你的问题!

评论后编辑

我认为你的观点是行为互相取代而不是链接。这确实是装饰器类的典型问题。但是你真的不应该在装饰器类中更改原始类。只有装饰师类装饰'输出原件。下面是一个典型的示例,说明装饰器模式将如何在您提到的真实世界场景中使用:

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();
//...

答案 1 :(得分:2)

仍然有点不完整,但它基本上完成了所有事情:

  1. 抽象所有扩展的组件类。
  2. 抽象Decorator类,修改扩展Component的类。
  3. 这是很多代码,所以这里是pastebin链接:

    [旧] http://pastebin.com/mz4WKEzD

    [新] http://pastebin.com/i7xpYuLe

    <强>零件

    1. 可以互相延伸
    2. 可以修改/添加/删除属性
    3. 可以与装饰者共享属性
    4. <强>装修

      1. 可以将功能附加到组件
      2. 可以修改/添加/删除组件属性
      3. 示例输入

        $Sugar = 1;
        $DoubleSugar = 1;
        
        $Cofee = new SimpleCofee();
        $Tea   = new SimpleTea();
        
        $Cofee->Produce();
        $Tea->Produce();
        
        print "\n============\n\n";
        
        if($Sugar)
        {
            new SugarCube($Cofee);
            $Cofee->Produce();
            new SugarCube($Cofee);
            $Cofee->Produce();
        }
        
        if($DoubleSugar)
        {
            new SugarCube($Tea);
            $Tea->Produce();
            new SugarCube($Tea);
            $Tea->Produce();
        }
        

        <强>输出

        Making coffee....
        Adding Water: 150
        Making cofee: array (
          'cofeee' => 25,
        )
        Making tea....
        Adding Water: 150
        Making tea: array (
          'tea' => 25,
        )
        
        ============
        
        Making coffee....
        Adding sugar: 1
        Adding Water: 140
        Making cofee: array (
          'cofeee' => 25,
          'Spoon' => 1,
        )
        Making coffee....
        Adding sugar: 1
        Adding sugar: 1
        Adding Water: 120
        Making cofee: array (
          'cofeee' => 25,
          'Spoon' => 1,
        )
        Making tea....
        Adding sugar: 2
        Adding Water: 130
        Making tea: array (
          'tea' => 25,
          'Spoon' => 1,
        )
        Making tea....
        Adding sugar: 2
        Adding sugar: 2
        Adding Water: 90
        Making tea: array (
          'tea' => 25,
          'Spoon' => 1,
        )
        

        <强>更新

        这很疯狂,但现在孩子们可以超载父功能。最重要的是,您现在可以使用数组接口$this['var']来访问共享属性。哈希将自动且透明地添加。

        唯一的缺点是父母必须允许功能过载。

        新输出

        Making Cofee....
        Adding Water: 150
        Making Cofee: array (
          'cofeee' => 25,
        )
        Making Tea....
        Adding Water: 150
        Making Tea: array (
          'tea' => 25,
        )
        
        ============
        
        Making Cofee....
        Adding sugar: 1
        Adding Water: 140
        Making Cofee: array (
          'cofeee' => 25,
          'Spoon' => 1,
        )
        Making Cofee....
        Adding sugar: 1
        Adding sugar: 1
        Adding Water: 120
        Making Cofee: array (
          'cofeee' => 25,
          'Spoon' => 1,
        )
        
        I have take over Produce!
        But I'm a nice guy so I'll call my parent
        
        Making Tea....
        Adding sugar: 2
        Adding Water: 130
        Making Tea: array (
          'tea' => 25,
          'Spoon' => 1,
        )
        
        I have take over Produce!
        But I'm a nice guy so I'll call my parent
        
        Making Tea....
        Adding sugar: 2
        Adding sugar: 2
        Adding Water: 90
        Making Tea: array (
          'tea' => 25,
          'Spoon' => 1,
        )
        
        ============
        
        DoubleSugarCube::SuperChain(array (
          0 => 'test',
        ))
        SugarCube::SuperChain(array (
          0 => 'DoubleSugarCube',
        ))
        SimpleTea::SuperChain(array (
          0 => 'SugarCube',
        ))
        SimpleCofee::SuperChain(array (
          0 => 'SimpleTea',
        ))
        

        <强>更新

        这是我的最终草案。我不能一点一点地改变我的解决方案。如果出现错误,请将其全部列入清单。

        删除了callparent并将其所有功能放入parent::function

        1. 儿童可以使用父母的财产。
        2. 孩子可以超载父功能。
        3. 重载将从基类开始一直到abstract class Decorator类。然后从传递给构造函数的父元素中获取属性/方法。
        4. 你说你喜欢你的财产分享方法。所以我没有打扰回答这个问题。
        5. 我希望你现在接受答案。如果没有,那么我期待你的。我希望当你解决所有问题时,你会与我们其他人分享。

          干杯

答案 2 :(得分:1)

有一个适合咖啡机问题的解决方案

abstract class Coffee { 
    protected $cup = array();
    public function getCup() {
        return $this->cup;
    } 
}   
class SimpleCoffee extends Coffee {
    public function __construct() {
        $this->cup['coffeePowder'] = 1;
        $this->cup['water']        = 20;
        $this->cup['spoon']        = FALSE;
    }
}

abstract class Decorator extends Coffee { 
    private $_handler = null;

    public function __construct($handler) {
        $this->_handler = $handler;
        $this->cup      = $handler->cup;
    }
}

class SugarCoffee extends Decorator {
    public function __construct($handler) {
        parent::__construct($handler);
        $this->cup['water'] -= 1;
        $this->cup['sugar']  = 1;
        $this->cup['spoon']  = TRUE;
    }
}
class MilkCoffee extends Decorator{
    public function __construct($handler) {
        parent::__construct($handler);
        $this->cup['water'] -= 5;
        $this->cup['milk']    = 5;
    }
}

$wantSugar = TRUE;
$wantMilk  = TRUE;

$coffee = new SimpleCoffee();
if($wantSugar)
    $coffee = new SugarCoffee($coffee);
if($wantMilk)
    $coffee = new MilkCoffee($coffee);

$cupOfCoffee = $coffee->getCup();

var_dump($cupOfCoffee);

还有另一个real world example,我希望它可以帮到你:

abstract class MessageBoardHandler {
    public function __construct(){}
    abstract public function filter($msg);
}
class MessageBoard extends MessageBoardHandler {
    public function filter($msg) {
        return "added in messageBoard|".$msg;
    }
}
class MessageBoardDecorator extends MessageBoardHandler {
    private $_handler = null;
    public function __construct($handler) {
        parent::__construct(); 
        $this->_handler = $handler;
    }
    public function filter($msg) {
        return $this->_handler->filter($msg);
    } 
}
class HtmlFilter extends MessageBoardDecorator {
    public function __construct($handler) {
        parent::__construct($handler);
    } 
    public function filter($msg) {
        return "added in html filter|".parent::filter($msg);
    }   
}   
class SensitiveFilter extends MessageBoardDecorator {
    public function __construct($handler) {
        parent::__construct($handler);
    }   
    public function filter($msg) {
        return "added in sensitive filter|".parent::filter($msg);
    }   
}
$html      = TRUE;
$sencitive = TRUE;
$obj = new MessageBoard();
if($html) {
    $obj = new SensitiveFilter($obj);
}
if($sencitive) {
    $obj = new HtmlFilter($obj);
}
echo $obj->filter("message");