php中装饰器的完整继承行为
Posted
技术标签:
【中文标题】php中装饰器的完整继承行为【英文标题】:full inheritance behaviour with Decorator in php 【发布时间】:2013-08-09 11:08:47 【问题描述】:我不太习惯设计模式,也从未使用过装饰器。我想要一个可以根据上下文具有不同行为的对象。这些行为在不同的类中定义。我猜装饰器可以解决问题。但我需要每个装饰器都可以访问相同的属性,并首先调用子方法,就像继承一样。所以这就是我所做的:
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->父母很丑 $this->这太丑了你怎么看?
如何以其他方式访问装饰器(“子”)方法 如何共享属性,例如它们是否在继承的上下文中受到保护 Decorator 是不是不好用? 有没有更优雅的模式可以解决问题 parent 和这个属性是一种重新发明***,不是吗?现实世界的例子:咖啡机
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();
//...
【问题讨论】:
【参考方案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");
【讨论】:
因为它破坏了装饰器链接。对于链中的第一个(我称为“Foo”的)没关系,它将始终被实例化,但对于其他(Bar、Baz 等),继承链必须根据上下文以编程方式定义.如果你这样硬编码(真的是你的意思吗?):class Baz extends Bar...
它变成了经典的“静态”继承。无论如何,谢谢您的回答,如果您想编辑或添加新的答案,欢迎您。
我更新了源代码。不知道是不是你想要的。
是的!更像是这样。但是你说装饰器链接没有被破坏,它是。行为的抽象是我想要的,但不仅如此。我希望根据上下文,对象可以始终是一个组件,有时是一个 Foo,有时是一个 Bar(你所做的),有时是一个 Foo 和一个 Bar(使用 Bar“扩展”Foo,你还没有做)。我喜欢你的努力,继续。
哦。我知道。所以你不需要抽象类Component
。但是Component
的实例在它变成Component
的子类的实例之前?
是的,你是对的,这是一种实现方式,我选择的方式,经典方式,装饰模式方式。使用这种模式,组件是抽象的,因为您可以定义必须在子类中定义的抽象函数(未实现)。但在我的示例中它不是强制性的(因为总是有一个 foo 实例),我想你已经很好地理解了我想要做什么。目标只是做一种动态继承。您是否在问题中看到了我的“现实世界示例”(咖啡机)?它也许可以帮助你理解我想要什么。您也可以阅读 Mohamed 的回复。【参考方案2】:
装饰器模式不是为了在基类中进行内部更改(你称之为一个父类)。您正在做的是对这种模式的错误使用。装饰器应该只改变函数的输出而不是使用变量。
一种解决方案是为受保护的变量定义 getter 和 setter,并从装饰器中调用它们。
另一个解决方案是我个人更喜欢的,那就是拆分依赖于上下文和基类的行为:
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);
我希望这能回答你的问题!
在 cmets 之后编辑
我明白你的观点,即行为相互替换而不是链接。这确实是装饰器类的典型问题。但是,您真的不应该更改装饰器类中的原始类。装饰器类仅“装饰”原始输出。下面是一个典型示例,说明如何在您提到的现实世界场景中使用装饰器模式:
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();
//...
【讨论】:
不错的答案! Mohamed,我看到你是一个优秀的工程师,而且你知道你在说什么。我会尝试做出同样质量的回应。首先,您的代码不能正常工作。添加 BazBehaviour 时,$obj 忘记了 BarBehaviour,这不是我想要的。如果上下文说需要使用 Bar,那么即使是“somethingElse()”,也需要使用 BazBehaviour。 这取决于 getProp2Value() 实现,但例如,如果 useBar( ) 和其他东西()。如果 useBar() 为假但 somethingElse() 为真:“在标准中设置,在 Baz 中修改”。通过抽象的 fooBar 示例可能不容易看出,我将添加一个具体的示例。在那之后,我会评论你说的:“我做的不是很好地使用装饰器,我不尊重封装原则,我应该使用getter和setter”。 我不会就这个主题进行辩论,但是恕我直言,尤其是在 php 中,getter 和 setter 是邪恶的(你可以在 google 上搜索,关于这个主题已经说了很多)。对我来说,如果一个对象属性可以是公共的、受保护的或私有的,那么我们按预期使用它是有意义的:有些属性需要在所有项目中访问,有些只能由子类访问,有些必须在班上。无论如何,它不影响这件事。你说装饰器不是好的模式,我应该使用哪个?或者可能是哪种模式组合? 您的解决方案不是严格意义上的装饰器,因为装饰器需要可以添加行为,而在您的解决方案中,它可以被更改,链接功能被破坏。 谢谢!我不鼓励你在这个解决方案中使用setter和getter,我没有使用Decorator模式,代码中使用的模式是Mediator模式的一种形式。【参考方案3】:仍然有点不完整,但它基本上可以做所有事情:
-
一切都扩展的抽象组件类。
抽象装饰器类,修改扩展组件的类。
代码很多,所以这里是 pastebin 链接:
[旧]http://pastebin.com/mz4WKEzD
[新]http://pastebin.com/i7xpYuLe
组件
-
可以相互扩展
可以修改/添加/删除属性
可以与装饰器共享属性
装饰器
-
可以将函数附加到组件
可以修改/添加/删除组件属性
示例输入
$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
-
孩子可以访问父母的财产。
子级可以重载父级函数。
重载将从基类开始一直到
abstract class Decorator
类。然后从传递给构造函数的父级获取属性/方法。
您说您喜欢您的财产共享方法。所以我懒得回答了。
我希望你现在能接受这个答案。如果没有,那么我期待你的。我希望当您整理好所有内容时,您会与我们其他人分享。
干杯
【讨论】:
我不得不说...哇! mAsT3RpEE,你在赏金结束前 17 小时到达这里,你理解了问题的大部分,并在第一个答案上给出了一个聪明的解决方案。为此,+1。但是......(是的,有一个“但是”,而不仅仅是一个)我更喜欢我的共享属性的解决方案(我猜你的解决方案工作,但对我来说,哈希方式并不是很漂亮)。一个孩子不能替换他的父方法,并且只调用他的直接父方法而不调用其他链式装饰器。 你使用我想避免的->parent
(我猜,如果你想解决“孩子不能替换他的父方法”的问题,->this
)。更一般地说,您的代码不干净,您需要在装饰器中使用大量 Xtra 代码。但如果你能改进它,你将赢得+50。但我不满足,你只是尽力了。
如果你没有时间改进所有,只关注方法(属性不是强制性的),如果你能找到$this->parent
和$this->this
的解决方案(替换为parent::
和 $this
),您的答案将被接受。
@Pierre 关于$hash
?这个很重要。哈希使子类无法访问父类属性。它要么必须重新散列父类,要么使用数据散列值。我特地把它放在那里是出于我自己的原因。
@Pierre 我会让散列自动进行。所以你可以使用 $this['coffee'] 而不是 $this[$hash.'coffee']。还要注意。 如果定义了父属性,子级只能覆盖父属性。否则,只会添加该属性的本地副本(这也是有意完成的)。如果您希望装饰器共享属性,则必须传递装饰器而不是父类。以上是关于php中装饰器的完整继承行为的主要内容,如果未能解决你的问题,请参考以下文章