Corrigindo uma grande desvantagem ao padrão decorador
Decidi há algum tempo, na refatoração de algum código de combate de jogo, tentar o padrão de decorador. Os combatentes podem ter várias habilidades passivas e também podem ser tipos diferentes de criaturas. Eu percebi que decorador me permite adicionar o comportamento em várias combinações em tempo de execução, então eu não preciso de centenas de subclasses.
Eu quase terminei de fazer os cerca de 15 decoradores das habilidades passivas, e nos testes descobri uma coisa - uma desvantagem bastante gritante para o padrão decorador que estou surpreso de não ter ouvido falar antes.
Para que os decoradores trabalhem, seus métodos devem ser chamados no decorador mais externo. Se a "classe base" - o objeto empacotado - chamar um de seus próprios métodos, esse método não será a sobrecarga decorada, já que não há como a chamada ser "virtualizada" para o wrapper. Todo o conceito de uma subclasse artificial é quebrado.
Isso é uma grande coisa. Meus combatentes têm métodos comoTakeHit
que por sua vez chamam deDamage
método. Mas o decoradoDamage
não está sendo chamado de jeito nenhum.
Talvez eu tenha escolhido o padrão errado ou tenha excesso de zelo em sua aplicação. Você tem algum conselho sobre um padrão mais apropriado nesta situação, ou uma maneira de contornar essa falha? O código que eu refatorei só tinha todas as habilidades passivas espalhadas por todo o código de combate dentroif
bloqueia em lugares aparentemente aleatórios, e é por isso que eu queria dividi-lo.
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;
}
Este método está na baseCombatant
classe.DamageReduction
eDamage
podem e são ambos substituídos em vários decoradores, por exemplo, um passivo que corta dano por um quarto, ou outro que reflete algum dano de volta ao atacante.
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;
}
}
Mas, novamente, esses métodos de decorador nunca são chamados, porque não são chamados de fora, são chamados dentro da classe base.