功能“实时”组合

时间:2018-06-13 19:18:22

标签: javascript functional-programming state

我最近遇到了great article,涵盖了对象组合VS传统继承的好处。

希望我的问题不会被标记为自以为是,但我想知道在对象根据用户的游戏交互进行更改时使用合成的好方法。

以文章代码为例:

const canCast = (state) => ({
    cast: (spell) => {
        console.log(`${state.name} casts ${spell}!`);
        state.mana--;
    }
})

const canFight = (state) => ({
    fight: () => {
        console.log(`${state.name} slashes at the foe!`);
        state.stamina--;
    }
})

const fighter = (name) => {
  let state = {
    name,
    health: 100,
    stamina: 100
  }

  return Object.assign(state, canFight(state));
}

const mage = (name) => {
  let state = {
    name,
    health: 100,
    mana: 100
  }

  return Object.assign(state, canCast(state));
}

scorcher = mage('Scorcher')
scorcher.cast('fireball');    // Scorcher casts fireball!
console.log(scorcher.mana)    // 99

slasher = fighter('Slasher')
slasher.fight();              // Slasher slashes at the foe!
console.log(slasher.stamina)  // 99

如何在运行时使用合成来更改Character对象的状态?我希望Character对象能够根据游戏事件进行更改,而不是已经存在的Mage对象。角色捡起一名工作人员,现在成为一名现在可以施放法术的“法师”。首先想到的是在角色中有一个状态属性,它根据交互而变化,而角色以某种方式“继承”现在施放并获得法术力状态属性的能力。

3 个答案:

答案 0 :(得分:1)

如果我理解你的问题,最好有一个功能对象组合,它有多种方法,如下所示。在这里,您可以创建单个对象,这些对象可以具有在下面的主对象中定义的一个或所有功能。基本上,can这里是提供操作方法集的函数,可以在运行时创建对象时使用。好吧,我在下面给出一些例子。希望这有助于

请注意,即使这是自以为是的

const can = (state) => {
 return {
    canFight : (spell) => {
      console.log(`${state.name} slashes at the foe!`);
        state.stamina--;
    },
   cast: (spell) => {
        console.log(`${state.name} casts ${spell}!`);
        state.mana--;
   }
 }
}

使用实例

const fighter = (name) => {
  let state = {
    name,
    health: 100,
    stamina: 100
  }

  return Object.assign(state, can(state));
}

const mage = (name) => {
  let state = {
    name,
    health: 100,
    mana: 100
  }

  return Object.assign(state, can(state));
}

const soldier = (name) => {
  let state = {
    name,
    health: 100,
    stamina: 100
  }

  return Object.assign(state, {fight: can(state).canFight(name)});
}

答案 1 :(得分:1)

decorator pattern完全解决了这种情况。

class Character {
  constructor(name) {
    this.name = name;
    this.health = 100;
    this.items = [];
  }
}

const fighterDecorator = character => {
  return Object.setPrototypeOf({
    character,
    stamina: 100,
    fight() {
      console.log(`${this.name} slashes at the foe!`);
      this.stamina--;
    }
  }, character);
}

const mageDecorator = character => {
  return Object.setPrototypeOf({
    character,
    mana: 100,
    cast(spell) {
      console.log(`${this.name} casts ${spell}!`);
      this.mana--;      
    }
  }, character);
}

let character = new Character("Bob");

// Can't fight; can't cast
// character.fight(); // TypeError: not a function
// character.cast(); // TypeError: not a function

// Character becomes a fighter at runtime
// Equiping an item and decorating new behavior are separate statements
character.items.push("sword");
character = fighterDecorator(character);
character.fight();              // Bob slashes at the foe!
console.log(character.stamina)  // 99
console.log(character.items)    // ["sword"]

// Character becomes normal unit again
// Remove decoration and remove item
character = character.character;
character.items = character.items.filter(item => item !== "sword");

// Once again, can't fight, can't cast
// character.fight(); // TypeError: not a function
// character.cast(); // TypeError: not a function

// Character becomes a mage at runtime
// Equiping an item and decorating new behavior are separate statements
character.items.push("staff");
character = mageDecorator(character);
character.cast("fireball");  // Bob casts fireball!
console.log(character.mana)  // 99
console.log(character.items) // ["staff"]

答案 2 :(得分:0)

对于这个特殊问题,最好使用duck typing而不是对象组合。鸭子打字使用duck test来确保类型安全:

  

如果它看起来像鸭子,像鸭子一样游泳,像鸭子一样呱呱叫,那么它可能 是鸭子。

对于这个问题,我们将使用类似的" mage测试"和#34;战斗机测试"分别为:

  1. 如果它有法术力并持有一名工作人员,那么它就是法师。
  2. 如果它有耐力并持剑,那么它就是一个战士。
  3. 请注意,我们仍然可以使用对象组合来保持代码模块化。我们将为characterfightermage创建原型,然后将它们组合在一起以获得最终原型:

    const character = {
        health: 100,
        right: null,
        left: null,
        equip(item) {
            const {name, right, left} = this;
                 if (right === null) this.right = item;
            else if (left  === null) this.left  = item;
            else console.error(`${name} is already holding ${right} and ${left}.`);
        }
    };
    

    首先,我们有角色的原型。每个角色至少有四个属性:namehealthright(即右手配备的项目)和left(即左手配备的项目) )。我们提供healthrightleft的默认值。但是,我们不会为name提供任何默认值。因此,当我们创建一个新角色时,我们必须为它命名。

    const fighter = {
        stamina: 100,
        fight(foe) {
            const {name, stamina, right, left} = this;
            if (right !== "a sword" && left !== "a sword")
                console.error(`${name} is not holding a sword.`);
            else if (stamina === 0) console.error(`${name} has no stamina.`);
            else { this.stamina--; console.log(`${name} slashes at ${foe}.`); }
        }
    };
    

    然后,我们有战斗机的原型。请注意,由于战斗机也是一个角色,我们可以使用name方法中的rightleftfight属性。此外,战斗机具有stamina属性,其默认值为100.

    const mage = {
        mana: 100,
        cast(spell) {
            const {name, mana, right, left} = this;
            if (right !== "a staff" && left !== "a staff")
                console.error(`${name} is not holding a staff.`);
            else if (mana === 0) console.error(`${name} has no mana.`);
            else { this.mana--; console.log(`${name} casts ${spell}.`); }
        }
    };
    

    接下来,我们有法师的原型。像战士一样,法师也是角色,因此他们也可以利用特定于角色的属性。此外,法师具有mana属性,默认值为100.

    Object.assign(character, fighter, mage);
    
    Object.prototype.create = function (properties) {
        return Object.assign(Object.create(this), properties);
    };
    
    const gandalf = character.create({ name: "Gandalf" });
    
    gandalf.equip("a sword");
    gandalf.equip("a staff");
    
    gandalf.fight("the goblin");
    gandalf.cast("a blinding light");    
    

    最后,我们使用Object.assign通过使用characterfighter原型扩展mage原型来组合所有原型。我们还使用有用的Object.prototype函数扩展create以轻松创建原型实例。我们使用这个方法来创建一个名为Gandalf的character实例,我们让他与一个妖精战斗。

    
    
    const mage = {
        mana: 100,
        cast(spell) {
            const {name, mana, right, left} = this;
            if (right !== "a staff" && left !== "a staff")
                console.error(`${name} is not holding a staff.`);
            else if (mana === 0) console.error(`${name} has no mana.`);
            else { this.mana--; console.log(`${name} casts ${spell}.`); }
        }
    };
    
    const fighter = {
        stamina: 100,
        fight(foe) {
            const {name, stamina, right, left} = this;
            if (right !== "a sword" && left !== "a sword")
                console.error(`${name} is not holding a sword.`);
            else if (stamina === 0) console.error(`${name} has no stamina.`);
            else { this.stamina--; console.log(`${name} slashes at ${foe}.`); }
        }
    };
    
    const character = {
        health: 100,
        right: null,
        left: null,
        equip(item) {
            const {name, right, left} = this;
                 if (right === null) this.right = item;
            else if (left  === null) this.left  = item;
            else console.error(`${name} is already holding ${right} and ${left}.`);
        }
    };
    
    Object.assign(character, fighter, mage);
    
    Object.prototype.create = function (properties) {
        return Object.assign(Object.create(this), properties);
    };
    
    const gandalf = character.create({ name: "Gandalf" });
    
    gandalf.equip("a sword");
    gandalf.equip("a staff");
    
    gandalf.fight("the goblin");
    gandalf.cast("a blinding light");
    
    
    

    以上是整个脚本的演示,展示了它的工作原理。如您所见,您可以将角色原型分解为几个不同的原型,例如magefighter,然后使用Object.assign将它们全部重新组合在一起。这使得添加新字符类型更容易,更易于管理。鸭子打字用于确保战斗机(配备剑的角色)不能施放咒语等。希望有所帮助。