I decided some time ago, in refactoring some game code, to try a sample decorator. Combatants can have different passive abilities, and can also be different types of creatures. I realized that a decorator allows me to add behavior to various combinations at runtime, so I don't need hundreds of subclasses.
I almost finished making 15 or so decorators for passive abilities, and when testing, I found something - a rather egregious flaw for the decorator, which I am surprised that I have not heard about before.
In order for decorators to work in general, their methods must be called on an external decorator. If the "base class" - the wrapped object - calls one of its methods, this method will not be decorated with overload, since there is no way to "virtualize" the call into the shell. 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 method Damage. But decorated Damageis not called at all.
Perhaps I chose the wrong template or was excessive in my application. Do you have any advice on a more suitable template in this situation or a way around this? The code I reorganized from everything had all the passive abilities sprinkled throughout the battle code inside the blocks ifin seemingly random places, so I wanted to break it.
edit: 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 class Combatant. DamageReductionand Damagecan and can be overridden in different decorators, for example, a passive that deals damage by a quarter or another, which affects some damage 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 then again, these decorator methods are never called, because they are not called from the outside, they are called inside the base class.