Некоторое время назад при рефакторинге боевого кода игры я решил попробовать шаблон декоратора. У бойцов могут быть различные пассивные способности, а также они могут быть разными типами существ. Я понял, что декоратор позволяет добавлять поведение в различных комбинациях во время выполнения, поэтому мне не нужны сотни подклассов.
Я почти закончил делать 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;
}
}
Но опять же, эти методы декоратора никогда не вызываются, потому что они вызываются не извне, а внутри базового класса.