本文介绍了纠正装饰图案的一大缺点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我决定一会儿,重构一些游戏作战代码,尝试装饰图案。战斗人员可以拥有各种被动能力,也可能是不同类型的生物。我想,装饰器让我在运行时添加各种组合的行为,所以我不需要数百个子类。



我几乎完成了15左右被动能力的装饰师,在测试中,我发现了一些相当明显的缺点,装饰师模式让我很惊讶,我以前没有听说过。



装饰工作者根本不用工作,他们的方法必须在最外面的装饰器上调用。如果基类 - 包装对象 - 调用其自己的方法之一,该方法将不会是装饰的重载,因为没有办法将调用虚拟化到包装器。人造子类的整体概念分解。



这是一件很大的事情。我的战斗人员有一些方法,例如 TakeHit ,这些方法又称为自己的 Damage 方法。但是装饰的 Damage 根本没有被调用。



也许我选择了错误的模式在其应用中过热。在这种情况下,您是否有更合适的方式提供任何建议,或是解决这个缺陷的方法?如果在看似随机的地方,如果阻塞,所有被动能力都会在内的战斗代码上洒下来,所以我想把它打破。 / p>

编辑:一些代码



  public function TakeHit($ attacker,$质量,$损坏)
{
$ damage - = $ this-> DamageReduction($ damage);

$ damage = round($ damage);

if($ damage< 1)$ damage = 1;

$ this->损害($ damage);

if($ damage> 0)
{
$ this-> wasHit = true;
}

返回$损坏;
}

这个方法在基础 Combatant 类。 DamageReduction 伤害可以并且都被覆盖在各种装饰器中,例如被动的,可以将伤害减少四分之一,或另一个反映了攻击者的一些伤害。

  class Logic_Combatant_Metal extends Logic_Combatant_Decorator 
{
public function TakeHit ($ attacker,$ quality,$ damage)
{
$ actual = parent :: TakeHit($ attacker,$ quality,$ damage);

$ reflect = $ this-> MetalReflect($ actual);
if($ reflect> 0)
{
Data_Combat_Event :: Create(Data_Combat_Event :: METAL_REFLECT,$ target-> ID(),$ attacker-> ID(),$反映);
$ attacker-> Damage($ reflect);
}

return $ actual;
}

私有函数MetalReflect($ damage)
{
$ reflect = $ damage *((($ this-> Attunement()/ 100)* (METAL_REFLECT_MAX - METAL_REFLECT_MIN))+ METAL_REFLECT_MIN);
$ reflect = ceil($ reflect);

return $ reflect;
}
}

但是,这些装饰器方法再也不会被调用,因为它们没有从外部被调用,它们被称为基类。

解决方案

tl; dr:A装饰器是为了改变一个对象或者函数的行为,但是它不会覆盖原来的行为,就像子类化。

如果我正确理解,你会这样说 -



decorated_thingy_instance = DecoratorA(OriginalThingy))



给定

  DecoratorA {
decoratedThingy = ...;
doStuff(){
decoratedThingy.doStuff()
...
}
doOtherStuff(){
decoratedThingy.doOtherStuff()
...
}
}

  OriginalThingy {

doStuff(){
this.doOtherStuff()
}
doOtherStuff (){
...
}
}

您的问题是DecoratorA的doOtherStuff不被调用。最好是考虑应用于函数的装饰器而不是对象,而不是完全像子类化。原则上,每个装饰器的行为不应该影响其他装饰器或内部对象的行为,原因与您提到的相同,您无法像子类那样更改控件流。



在实践中,这意味着您可以更改界面公开的功能的结果(将输出乘以2),但不能更改包装类计算功能。你可以使一个完全丢弃包装类的输出的包装器,或者不完全调用它,例如,

  DevNullDecorator { 

decoratedThingy = new Thingy();
doStuff(){
//decoratedThingy.doStuff()
//做任何你想要的
}
doOtherStuff(){
...
}
}

但这或多或少打破了模式的精神。如果要修改内部对象本身,则需要在接口中使用getter和setter来编写方法,或多或少打破了模式的精神,但可能适用于您的案例。


I decided a while back, in refactoring some game combat code, to try the decorator pattern. The combatants can have various passive abilities, and they may also be different types of creatures. I figured that decorator lets me add on behavior in various combinations at run time so I don't need hundreds of subclasses.

I've almost finishing making the 15 or so decorators for the passive abilities, and in testing I discovered something - a rather glaring disadvantage to the decorator pattern that I'm surprised I haven't heard of before.

For the decorators to work at all, their methods must be called on the outermost decorator. If the "base class" - the wrapped object - calls one of its own methods, that method won't be the decorated overload, since there's no way for the call to be "virtualized" to the wrapper. The whole concept of an artificial subclass breaks down.

This is kind of a big deal. My combatants have methods like TakeHit which in turn call their own Damage method. But the decorated Damage isn't getting called at all.

Perhaps I've chosen the wrong pattern or been overzealous in its application. Do you have any advice on a more appropriate pattern in this situation, or a way to work around this flaw? The code that I refactored from just had all the passive abilities sprinkled all over the combat code inside if blocks in seemingly random places, so that's why I wanted to break it out.

edit: some code

public function TakeHit($attacker, $quality, $damage)
{
    $damage -= $this->DamageReduction($damage);

    $damage = round($damage);

    if ($damage < 1) $damage = 1;

    $this->Damage($damage);

    if ($damage > 0)
    {
        $this->wasHit = true;
    }

    return $damage;
}

This method is in the base Combatant class. DamageReduction and Damage can and are both overridden in various decorators, for example a passive that cuts damage by a quarter, or another that reflects some damage back to the attacker.

class Logic_Combatant_Metal extends Logic_Combatant_Decorator
{
    public function TakeHit($attacker, $quality, $damage)
    {
        $actual = parent::TakeHit($attacker, $quality, $damage);

        $reflect = $this->MetalReflect($actual);
        if ($reflect > 0)
        {
            Data_Combat_Event::Create(Data_Combat_Event::METAL_REFLECT, $target->ID(), $attacker->ID(), $reflect);
            $attacker->Damage($reflect);
        }

        return $actual;
    }

    private function MetalReflect($damage)
    {
        $reflect = $damage * ((($this->Attunement() / 100) * (METAL_REFLECT_MAX - METAL_REFLECT_MIN)) + METAL_REFLECT_MIN);
        $reflect = ceil($reflect);

        return $reflect;
    }
}

But again, these decorator methods are never getting called, because they aren't being called from the outside, they're called inside the base class.

解决方案

tl;dr: A decorator is meant to change the behavior of an object or function, but it does not override the behavior of the original like subclassing does.

If I'm understanding correctly, you're saying this -

decorated_thingy_instance = DecoratorA(OriginalThingy))

given

DecoratorA{
    decoratedThingy = ...;
    doStuff(){
      decoratedThingy.doStuff()
      ...
    }
    doOtherStuff(){
      decoratedThingy.doOtherStuff()
        ...
    }
}

and

 OriginalThingy{

    doStuff(){
       this.doOtherStuff()
    }
    doOtherStuff(){
       ...
    }
}

Your problem is that DecoratorA's doOtherStuff isn't called. It's better to think of decorators applied to functions than objects and it's not exactly like subclassing. In principle, the behavior of each decorator shouldn't affect that of the other decorators or the inner object for the same reason you mentioned, you can't alter the control flow like you would a subclass.

In practice, this means you can alter the result of the functions exposed by the interface (multiply the output by 2), but you can't change how the wrapped class calculates the function. You could make a wrapper that completely discards the output of the wrapped class or doesn't call it altogether, e.g,

DevNullDecorator{

    decoratedThingy = new Thingy();
    doStuff(){
      //decoratedThingy.doStuff()
      // do whatever you want
    }
    doOtherStuff(){
       ...
    }
}

But this more or less breaks the spirit of the pattern. If you want to modify the inner object itself, you'll need to write methods in the interface with getters and setters, this also more or less breaks the spirit of the pattern, but might work for your case.

这篇关于纠正装饰图案的一大缺点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-03 05:53