如果没有示例,这将很难解释,所以我将使用here中的那个。
const canCast = (state) => ({
cast: (spell) => {
console.log(`${state.name} casts ${spell}!`);
state.mana--;
}
})
const mage = (name) => {
let state = {
name,
health: 100,
mana: 100
}
return Object.assign(state, canCast(state));
}
简单地说,我们有一个'法师'对象和'演员'行为。
现在说我们想拥有一种新型法师,可以通过施法来消耗对手的生命值。起初看起来很容易;只需创建一个新行为:
const canCastDrain = (state) => ({
cast: (spell) => {
console.log(`${state.name} casts ${spell}!`);
state.mana--;
target.health--;
state.health++;
}
})
然而,这迫使我们复制原始的演员代码。你可以想象,对于更复杂的行为,这将是一个巨大的问题。如何避免这种情况?
如果我们使用经典继承,那么排出法术可以扩展基础强制转换,然后调用父方法。但后来我们陷入了继承问题。如果我们添加新的法术强制转换,则很难混合和匹配它们。
答案 0 :(得分:1)
这个答案是以JS 伪代码的形式给出的,因为我对面向对象的JS(我通常使用TS)没有信心。
你的法师可能有一个角色基类,或者什么。毕竟,每个人都有自己的名字和健康。我已经省略了,因为它与答案无关。问题是你的法术是如何构建的。
我非常自信地认为命令模式是你需要的。
Mage有一些属性,以及两种铸造方法。第一个确定法师是否可以施放该法术。你可以让法术有一个类别(或拼写学校),或者你想要检查权限。
预演和演员表的方法可能会出现,但不是明确的问题的一部分。也许该咒语需要在调用其施法方法之前检查目标是否有效。
class Mage {
mana: number;
health: number;
name: string;
canCast(spell) {
// check if the mage knows the spell, or knows the school of magic, or whatever.
// can also check that the mage has the mana, though since this is common to every cast and doesn't vary, that can be moved into the actual cast method.
// return true/false
// this method can vary as needed
}
// should be the same for all mages.
// we call the spells pre-cast hooks before casting, for composite spells this ensures each sub-spell pre-hook is called before any of the spells
// are cast. This hook can be used to verify the spell *can* be cast (e.g. you have enough health)
cast(spell, target) {
if (spell.getCost() > mana) {
// not enough mana.
// this isn't part of canCast, because this applies to every mage, and canCast can vary.
// return or throw an error
}
console.log("Casting....");
if (!spell.preCast(this, target)) {
// maybe the target isn't valid for this spell?
// we do this before *any* spells are cast, so if one of them is not valid,
// there's nothing to "roll back" or "undo".
// either throw an error or return. either way, don't continue casting.
}
spell.cast(this, target);
spell.postCast(this, target);
this.deductMana(spell.getCost());
console.log("Done casting. Did we win?");
}
}
基础法术,没有功能但充满了所谓的“爱”:
class Spell {
getName(): string;
getCost(): number;
preCast(target, caster, etc.) {}
cast(target, caster, etc.) {}
postCast(target, caster, etc.) {}
}
你的复合法术。除非你需要非常专业的东西,否则一个班级应该让你做任意数量的组合。例如,结合两个火焰法术可能会放大伤害,同时降低总法术力费用。这需要一个特殊的复合法术,SynergizingCompositeSpell
可能吗?
class CompositeSpell : Spell {
spells: Spell[];
getName {
// return the subspell names
}
getCost (
// return sum of subspell costs.
}
preCast(target, caster, etc.) {
// call each subspell's preCast
// if any return false, return false. otherwise, return true.
}
cast(target, caster, etc.) {
// call each spell's cast
}
postCast(target, caster, etc.) {
// call each spells post-cast
}
constructor(spell, spell, spell, etc). // stores the spells into the property
}
一个示例法术:
class Drain : Spell {
getName() { return "Drain!"; }
getCost() { return 3; } // costs 3 mana
cast(target, caster, etc.) {
target.harm(1); // let the target deduct its hp
console.log(`The ${target.name} was drained for 3 hp and looks hoppin' mad.`)
}
}
使用它的方式,施放一个能够消耗健康并使我的牙齿闪亮和镀铬的咒语
var mage = ... // a mage
var target = ... // you, obviously
var spellToCast = new CompositeSpell(new Drain(), new ShinyAndChrome());
mage.cast(spellToCast, target);
CompositeSpell
构造函数可以检查它给出的法术是否“兼容”,无论你的游戏中可能意味着什么。法术也可以使用canBeCastWith(spell)
方法来验证兼容性。也许将Drain
和Heal
组合在一起毫无意义,不应该被允许?或者一个咒语接受目标,但另一个咒语不接受?
值得注意的是,preCast
/ cast
/ postCast
方法应采用相同的参数,即使它们并非总是需要。你正在使用一种适合所有类型的模式,因此你需要包含任何法术可能需要的所有东西。我想这个列表仅限于:
我想指出的一件事是,不是直接使用健康或法术力加法/减法(例如state.mana--
),而是使用函数调用(例如state.useMana(1)
。你的选择随着未来的发展而开放。
例如,如果你的法师在他/她的健康状况降低时具有触发的能力怎么办?该法术不知道它应该触发任何东西。这取决于角色。这也允许您覆盖该方法,这是通过简单的加法/减法无法做到的。
我希望这个答案有所帮助。