我一般不习惯设计模式,我从未使用过装饰器。我想要一个根据上下文可以有不同行为的对象。这些行为在不同的类中定义。我猜装饰师会做到这一点。但我需要每个装饰器都可以访问相同的属性,并首先调用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();
//...
它做我想要的,但是:
你怎么看?
一个真实世界的例子:咖啡机
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();
//...
答案 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)
仍然有点不完整,但它基本上完成了所有事情:
这是很多代码,所以这里是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
类。然后从传递给构造函数的父元素中获取属性/方法。我希望你现在接受答案。如果没有,那么我期待你的。我希望当你解决所有问题时,你会与我们其他人分享。
干杯
答案 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");