【发布时间】:2012-08-15 07:15:45
【问题描述】:
不久前,我决定在重构一些游戏战斗代码时尝试使用装饰器模式。战斗者可以拥有各种被动能力,也可以是不同类型的生物。我认为装饰器可以让我在运行时以各种组合添加行为,因此我不需要数百个子类。
我几乎已经完成了为被动技能制作 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 中。 DamageReduction 和 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;
}
}
但同样,这些装饰器方法永远不会被调用,因为它们不是从外部调用的,而是在基类内部调用的。
【问题讨论】:
-
你能发布一些代码吗?
-
你能发布完整的列表吗?如果要覆盖 Damage 和 DamageReduction,则需要适当地调用包装类
-
@dfb 我愿意,就像在你的回答中一样,装饰器只是调用所有包装的方法。
-
我今天正面临着确切的问题。我找到的唯一解决方案是 CQRS,基本上意味着一类 - 一种方法。因为一旦你有两个,一个调用另一个,整个装饰器就停止工作了。
标签: php design-patterns decorator