如何按共同特征组织数据?

How do I organize data by common traits?

我在以允许我通过数据的共同描述符或特征引用数据的方式对数据进行编目时遇到问题。我很清楚继承、特征(编程概念)和接口,但是 none 这些似乎是我问题的正确答案。

我正在 JavaScript 中编写一个程序,其中可能包含许多不同的项目或对象。假设我有一个 WoodenShortSword 的数据类型,我想表达它具有 FlammableWeaponOneHanded 的特征。然后,我想定义一个函数,它仅将 OneHandedWeapon 的对象作为参数。或者,也许只有 FlammableWearable,或 Flammable 不是 Weapon.

的对象

我该怎么做?

到目前为止,我已经研究了 JavaScript 和 TypeScript 中的继承,这在技术上是可行的,但需要一堆中间 类 因为不允许多重继承。像 FlammableWeaponOneHandedWeapon。那很麻烦而且不理想。

我查看了 TypeScript 的抽象 类 和接口,但这些更多是关于共享功能,而不是描述事物。而且没有内置方法可以让我在运行时检查对象是否满足接口。

我还查看了 tcomb 库。尽管像我描述的那样的系统是可能的,但它仍然非常麻烦且容易出错。

JavaScript 对象是可扩展的,因此像 thing.Flammable=true 这样的表达式是有效的并且可以工作。

要测试对象是否具有 属性,您可以使用 thing.hasOwnProperty('property')。这比 thing 中的 'property 更好,因为后者将包含原型链。

函数可以按如下方式工作:

function doit(object) {
    if(!object.hasOwnProperty('Flammable') return;
    //  etc
}

这样一个对象就可以拥有多个特征,而不必费心伪造多重继承。

如果@Manngo 的方法还不是解决方案,可以考虑给予 这个答案需要 10 到 15 分钟的阅读时间。它实现了@Manngo 的方法,但侧重于 关于解决常见的组合冲突,如果涉及到组合的创建 来自有状​​态 mixins/traits.

的类型

根据 OP 对所需特征的描述,可以很容易地找到 基于函数的 mixin/trait 方法。从而实现细粒度 composable/reusable 每个单元都描述了一个特定的行为集 不同的(封装的)数据。

人们会实施某种 flammableoneHanded 伴随的行为 通过例如一个 Weapon 基地 class.

但是从上面提到的所有内容组成一个 WoodenShortSword 并不像 正如人们第一眼看到的那样直截了当。可能有方法 来自 oneHandedWeapon 需要对彼此采取行动(封装) 状态例如更新武器的 isActivated 状态 takeInLeftHand oneHanded 的方法被调用,或者 visa verce 以防万一 武器的 deactivate 动作发生。然后很高兴得到更新 oneHanded.

的内部 isInHand 状态

一个可靠的方法是方法修改必须依赖 在样板代码上,除非 JavaScript 在某一天本地实现 Function.prototype[around|before|after|afterReturning|afterThrowing|afterFinally].

作为概念证明的更长的示例代码可能看起来像这样...

function withFlammable() {                                // composable unit of reuse (mixin/trait/talent).
  var
    defineProperty = Object.defineProperty,

    isInFlames = false;

  defineProperty(this, 'isFlammable', {
    value:      true,
    enumerable: true
  });
  defineProperty(this, 'isInFlames', {
    get: function () {
      return isInFlames;
    },
    enumerable: true
  });
  defineProperty(this, 'catchFire', {
    value: function catchFire () {
      return (isInFlames = true);
    },
    enumerable: true
  });
  defineProperty(this, 'extinguish', {
    value: function extinguish () {
      return (isInFlames = false);
    },
    enumerable: true
  });
}


function withOneHanded() {                                // composable unit of reuse (mixin/trait/talent).
  var
    defineProperty = Object.defineProperty,

    isInLeftHand = false,
    isInRightHand = false;

  function isLeftHanded() {
    return (isInLeftHand && !isInRightHand);
  }
  function isRightHanded() {
    return (isInRightHand && !isInLeftHand);
  }
  function isInHand() {
    return (isInLeftHand || isInRightHand);
  }

  function putFromHand() {
    return isInHand() ? (isInLeftHand = isInRightHand = false) : (void 0);
  }

  function takeInLeftHand() {
    return !isInLeftHand ? ((isInRightHand = false) || (isInLeftHand = true)) : (void 0);
  }
  function takeInRightHand() {
    return !isInRightHand ? ((isInLeftHand = false) || (isInRightHand = true)) : (void 0);
  }
  function takeInHand() {
    return !isInHand() ? takeInRightHand() : (void 0);
  }

  function switchHand() {
    return (
         (isInLeftHand && ((isInLeftHand = false) || (isInRightHand = true)))
      || (isInRightHand && ((isInRightHand = false) || (isInLeftHand = true)))
    );
  }

  defineProperty(this, 'isOneHanded', {
    value: true,
    enumerable: true
  });

  defineProperty(this, 'isLeftHanded', {
    get: isLeftHanded,
    enumerable: true
  });
  defineProperty(this, 'isRightHanded', {
    get: isRightHanded,
    enumerable: true
  });
  defineProperty(this, 'isInHand', {
    get: isInHand,
    enumerable: true
  });

  defineProperty(this, 'putFromHand', {
    value: putFromHand,
    enumerable: true,
    writable: true
  });

  defineProperty(this, 'takeInLeftHand', {
    value: takeInLeftHand,
    enumerable: true,
    writable: true
  });
  defineProperty(this, 'takeInRightHand', {
    value: takeInRightHand,
    enumerable: true,
    writable: true
  });
  defineProperty(this, 'takeInHand', {
    value: takeInHand,
    enumerable: true,
    writable: true
  });

  defineProperty(this, 'switchHand', {
    value: switchHand,
    enumerable: true
  });
}


function withStateCoercion() {                            // composable unit of reuse (mixin/trait/talent).
  var
    defineProperty = Object.defineProperty;

  defineProperty(this, 'toString', {
    value: function toString () {
      return JSON.stringify(this);
    },
    enumerable: true
  });
  defineProperty(this, 'valueOf', {
    value: function valueOf () {
      return JSON.parse(this.toString());
    },
    enumerable: true
  });
}


class Weapon {                                            // base type.
  constructor() {
    var
      isActivatedState = false;

    function isActivated() {
      return isActivatedState;
    }

    function deactivate() {
      return isActivatedState ? (isActivatedState = false) : (void 0);
    }
    function activate() {
      return !isActivatedState ? (isActivatedState = true) : (void 0);
    }

    var
      defineProperty = Object.defineProperty;

    defineProperty(this, 'isActivated', {
      get: isActivated,
      enumerable: true
    });

    defineProperty(this, 'deactivate', {
      value: deactivate,
      enumerable: true,
      writable: true
    });
    defineProperty(this, 'activate', {
      value: activate,
      enumerable: true,
      writable: true
    });
  }
}


class WoodenShortSword extends Weapon {                   // ... the
  constructor() {                                         // inheritance
                                                          // part
    super();                                              // ...

    withOneHanded.call(this);                             // ... the
    withFlammable.call(this);                             // composition
                                                          // base
    withStateCoercion.call(this);                         // ...

    var                                                   // ... the method modification block ...
      procedWithUnmodifiedDeactivate  = this.deactivate,
      procedWithUnmodifiedActivate    = this.activate,

      procedWithUnmodifiedPutFromHand = this.putFromHand,
      procedWithUnmodifiedTakeInHand  = this.takeInHand,

      procedWithUnmodifiedTakeInLeftHand  = this.takeInLeftHand,
      procedWithUnmodifiedTakeInRightHand = this.takeInRightHand;

    this.deactivate = function deactivate () {            // "after returning" method modification.
      var
        returnValue = procedWithUnmodifiedDeactivate();

      if (returnValue === false) {
          procedWithUnmodifiedPutFromHand();
      }
      return returnValue;
    };
    this.activate = function activate () {                // "after returning" method modification.
      var
        returnValue = procedWithUnmodifiedActivate();

      if (returnValue === true) {
          procedWithUnmodifiedTakeInHand();
      }
      return returnValue;
    };

    this.putFromHand = function putFromHand () {          // "after returning" method modification.
      var
        returnValue = procedWithUnmodifiedPutFromHand();

      if (returnValue === false) {
          procedWithUnmodifiedDeactivate();
      }
      return returnValue;
    };
    this.takeInHand = function takeInHand () {            // "after returning" method modification.
      var
        returnValue = procedWithUnmodifiedTakeInHand();

      if (returnValue === true) {
          procedWithUnmodifiedActivate();
      }
      return returnValue;
    };

    this.takeInLeftHand = function takeInLeftHand () {    // "before" method modification.
      if (!this.isInHand) {
          procedWithUnmodifiedActivate();
      }
      return procedWithUnmodifiedTakeInLeftHand();
    };
    this.takeInRightHand = function takeInRightHand () {  // "before" method modification.
      if (!this.isInHand) {
          procedWithUnmodifiedActivate();
      }
      return procedWithUnmodifiedTakeInRightHand();
    };
  }
}


var
  sword = new WoodenShortSword;

console.log('sword : ', sword);
console.log('(sword + "") : ', (sword + ""));
console.log('sword.valueOf() : ', sword.valueOf());
console.log('\n');

console.log('sword.isFlammable : ', sword.isFlammable);
console.log('sword.isInFlames : ', sword.isInFlames);
console.log('\n');
console.log('sword.isOneHanded : ', sword.isOneHanded);
console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');

console.log('sword.deactivate : ', sword.deactivate);
console.log('sword.activate : ', sword.activate);
console.log('\n');
console.log('sword.deactivate() : ', sword.deactivate());
console.log('sword.activate() : ', sword.activate());
console.log('sword.activate() : ', sword.activate());
console.log('\n');

console.log('sword.isFlammable : ', sword.isFlammable);
console.log('sword.isInFlames : ', sword.isInFlames);
console.log('\n');
console.log('sword.isOneHanded : ', sword.isOneHanded);
console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.switchHand() : ', sword.switchHand());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.takeInRightHand() : ', sword.takeInRightHand());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.putFromHand() : ', sword.putFromHand());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.takeInLeftHand() : ', sword.takeInLeftHand());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.deactivate() : ', sword.deactivate());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.activate() : ', sword.activate());
console.log('\n');

console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.switchHand() : ', sword.switchHand());
console.log('\n');

console.log('sword.isFlammable : ', sword.isFlammable);
console.log('sword.isInFlames : ', sword.isInFlames);
console.log('\n');
console.log('sword.isLeftHanded : ', sword.isLeftHanded);
console.log('sword.isRightHanded : ', sword.isRightHanded);
console.log('sword.isInHand : ', sword.isInHand);
console.log('\n');
console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.catchFire() : ', sword.catchFire());
console.log('\n');

console.log('sword.isFlammable : ', sword.isFlammable);
console.log('sword.isInFlames : ', sword.isInFlames);
console.log('\n');

console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.extinguish() : ', sword.extinguish());
console.log('\n');

console.log('sword.isFlammable : ', sword.isFlammable);
console.log('sword.isInFlames : ', sword.isInFlames);
console.log('\n');

console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');


console.log('sword.putFromHand() : ', sword.putFromHand());
console.log('\n');

console.log('sword.isActivated : ', sword.isActivated);
console.log('\n');
.as-console-wrapper { max-height: 100%!important; top: 0; }