3

私は一般的にパターンを設計することにあまり慣れておらず、Decorator を使用したことはありません。コンテキストに応じて異なる動作を持つオブジェクトが必要です。これらの動作は、異なるクラスで定義されています。Decorator がそのトリックを行うと思います。しかし、各デコレータが同じプロパティにアクセスでき、継承のように最初に子メソッドを呼び出せるようにする必要があります。だからここで私がやったこと:

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の使い方が悪いのでしょうか?
  • トリックを行うよりエレガントなパターンはありますか
  • 親とこの属性は一種の車輪の再発明ですね。

現実世界の例: コーヒー マシン

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

3 に答える 3

2

まだ少し不完全ですが、基本的にすべてを行います。

  1. すべてが拡張される抽象コンポーネント クラス。
  2. Component を拡張するクラスを変更する抽象 Decorator クラス。

コードが多いので、ペーストビンのリンクは次のとおりです。

【旧】http://pastebin.com/mz4WKEzD

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

コンポーネント

  1. お互いに伸ばすことができます
  2. プロパティを変更/追加/削除できます
  3. デコレータとプロパティを共有できます

デコレータ

  1. 関数をコンポーネントにアタッチできます
  2. コンポーネントのプロパティを変更/追加/削除できます

入力例

$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']を使用して共有プロパティにアクセスできるようになりました。ハッシュは自動的かつ透過的に追加されます。

唯一の欠点は、親が関数のオーバーロードを許可する必要があることです。

NEW 出力

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. あなたはあなたの財産共有の方法が好きだと言いました。だから、あえて答える気はありませんでした。

あなたが今答えを受け入れてくれることを願っています。そうでなければ、私はあなたを楽しみにしています。あなたがすべてを整理したら、それを私たちの残りの人と共有してくれることを願っています.

乾杯

于 2013-11-18T18:47:55.530 に答える
2

Decorator パターンは、基本クラスの内部変更を行うために作成されたものではありません (これを 1 つの親と呼びます)。あなたがしていることは、このパターンの悪い使い方です。デコレーターは、変数を操作するのではなく、関数の出力のみを変更する必要があります。

1 つの解決策は、保護された変数のゲッターとセッターを定義し、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);

これがあなたの質問に答えてくれることを願っています!

コメントの後に編集

連鎖するのではなく、動作が互いに置き換えられるというあなたの指摘を理解しています。これは実際、decorator クラスの典型的な問題です。ただし、デコレータ クラスで元のクラスを変更するべきではありません。デコレータ クラスは、オリジナルの出力を「装飾」するだけです。あなたが言及した実際のシナリオでデコレータパターンがどのように使用されるかの典型的な例を以下に示します。

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();
//...
于 2013-11-12T18:51:58.350 に答える