Tes*_*rex 6 php design-patterns decorator
我在一段时间内决定重构一些游戏战斗代码,尝试装饰模式.战斗员可以拥有各种被动技能,也可能是不同类型的生物.我认为装饰器允许我在运行时添加各种组合的行为,所以我不需要数百个子类.
我几乎完成了为被动技能制作大约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;
}
Run Code Online (Sandbox Code Playgroud)
此方法位于基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;
}
}
Run Code Online (Sandbox Code Playgroud)
但同样,这些装饰器方法永远不会被调用,因为它们不是从外部调用的,而是在基类内部调用它们.
tl;dr:装饰器旨在更改对象或函数的行为,但它不会像子类化那样覆盖原始对象或函数的行为。
如果“基类”(包装的对象)调用它自己的方法之一,则该方法将不会是修饰的重载,因为该调用无法“虚拟化”到包装器。人工子类的整个概念崩溃了。
如果我没理解错的话,你是在说——
decorated_thingy_instance = DecoratorA(OriginalThingy))
给定
DecoratorA{
decoratedThingy = ...;
doStuff(){
decoratedThingy.doStuff()
...
}
doOtherStuff(){
decoratedThingy.doOtherStuff()
...
}
}
Run Code Online (Sandbox Code Playgroud)
和
OriginalThingy{
doStuff(){
this.doOtherStuff()
}
doOtherStuff(){
...
}
}
Run Code Online (Sandbox Code Playgroud)
你的问题是 DecoratorA 的 doOtherStuff 没有被调用。最好将装饰器应用于函数而不是对象,并且它与子类化并不完全一样。原则上,每个装饰器的行为不应影响其他装饰器或内部对象的行为,原因与您提到的相同,您不能像子类那样改变控制流。
实际上,这意味着您可以更改接口公开的函数的结果(将输出乘以 2),但无法更改包装类计算函数的方式。您可以创建一个包装器,完全丢弃包装类的输出或完全不调用它,例如,
DevNullDecorator{
decoratedThingy = new Thingy();
doStuff(){
//decoratedThingy.doStuff()
// do whatever you want
}
doOtherStuff(){
...
}
}
Run Code Online (Sandbox Code Playgroud)
但这或多或少打破了模式的精神。如果您想修改内部对象本身,则需要在接口中使用 getter 和 setter 编写方法,这也或多或少地破坏了模式的精神,但可能适合您的情况。