具有重叠行为的对象组合

时间:2018-06-13 18:52:54

标签: javascript object composition

如果没有示例,这将很难解释,所以我将使用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++;
    }
})

然而,这迫使我们复制原始的演员代码。你可以想象,对于更复杂的行为,这将是一个巨大的问题。如何避免这种情况?

如果我们使用经典继承,那么排出法术可以扩展基础强制转换,然后调用父方法。但后来我们陷入了继承问题。如果我们添加新的法术强制转换,则很难混合和匹配它们。

1 个答案:

答案 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)方法来验证兼容性。也许将DrainHeal组合在一起毫无意义,不应该被允许?或者一个咒语接受目标,但另一个咒语不接受?

值得注意的是,preCast / cast / postCast方法应采用相同的参数,即使它们并非总是需要。你正在使用一种适合所有类型的模式,因此你需要包含任何法术可能需要的所有东西。我想这个列表仅限于:

  • 施法者
  • 目标
  • 区域(效力区域)
  • 该法术的选项(在“龙与地下城”中,施法者选择将其变形为某人)

我想指出的一件事是,不是直接使用健康或法术力加法/减法(例如state.mana--),而是使用函数调用(例如state.useMana(1)。你的选择随着未来的发展而开放。

例如,如果你的法师在他/她的健康状况降低时具有触发的能力怎么办?该法术不知道它应该触发任何东西。这取决于角色。这也允许您覆盖该方法,这是通过简单的加法/减法无法做到的。

我希望这个答案有所帮助。