纠正装饰器模式的一个大缺点

2024-06-19

不久前,我在重构一些游戏战斗代码时决定尝试装饰器模式。战斗者可以拥有各种被动能力,也可能是不同类型的生物。我认为装饰器可以让我在运行时以各种组合添加行为,因此我不需要数百个子类。

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

为了让装饰器正常工作,必须在最外面的装饰器上调用它们的方法。如果“基类”(包装的对象)调用它自己的方法之一,则该方法将不会是修饰的重载,因为无法将调用“虚拟化”到包装器。人工子类的整个概念崩溃了。

这是一件大事。我的战斗人员有类似的方法TakeHit进而调用自己的Damage方法。但装修的Damage根本没有被叫到。

也许我选择了错误的模式或者在它的应用中过于热心。对于这种情况下更合适的模式,或者解决此缺陷的方法,您有什么建议吗?我重构的代码只是将所有被动能力散布在里面的战斗代码中if块看似随机的地方,所以这就是我想把它打破的原因。

编辑:一些代码

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

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

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

但同样,这些装饰器方法永远不会被调用,因为它们不是从外部调用的,而是在基类内部调用的。


tl;dr:装饰器旨在更改对象或函数的行为,但它不会像子类化那样覆盖原始对象或函数的行为。

如果“基类”(包装的对象)调用它自己的对象之一 方法,该方法不会是修饰的重载,因为没有 将调用“虚拟化”到包装器的方式。整个概念 人工子类的崩溃。

如果我没理解错的话,你是在说——

decorated_thingy_instance = DecoratorA(OriginalThingy))

given

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

and

 OriginalThingy{

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

你的问题是 DecoratorA 的 doOtherStuff 没有被调用。最好将装饰器应用于函数而不是对象,并且它与子类化并不完全一样。原则上,每个装饰器的行为不应影响其他装饰器或内部对象的行为,原因与您提到的相同,您不能像子类那样改变控制流。

实际上,这意味着您可以更改接口公开的函数的结果(将输出乘以 2),但无法更改包装类计算函数的方式。您可以创建一个包装器,完全丢弃包装类的输出或完全不调用它,例如,

DevNullDecorator{

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

纠正装饰器模式的一个大缺点 的相关文章

随机推荐