【问题标题】:How do I organize data by common traits?如何按共同特征组织数据?
【发布时间】:2017-04-14 01:52:00
【问题描述】:

我无法以某种方式对数据进行编目,从而使我能够通过其通用描述符或特征来引用数据。我非常了解继承、特征(编程概念)和接口,但这些似乎都不是我问题的正确答案。

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

我该怎么做?

到目前为止,我已经研究了 JavaScript 和 TypeScript 中的继承,这在技术上是可行的,但由于不允许多重继承,因此需要一堆中间类。喜欢FlammableWeaponOneHandedWeapon。这既麻烦又不理想。

我查看了 TypeScript 的抽象类和接口,但它们更多的是关于共享功能,而不是描述事物。并且我无法看到在运行时检查对象是否满足接口的内置方法。

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

【问题讨论】:

  • 听起来您已经走在正确的轨道上。 JS 不支持多重继承,所以你必须采取另一种方法:stackoverflow.com/questions/9163341/…
  • 我在正确的轨道上吗?继承、特征和接口更多的是关于共享功能,而我基本上只对形容词感兴趣。
  • 你可以采用Mixins的方法,用ES6类来实现也不错。
  • 特性很像 JavaScript 中的 Mixins 是关于行为的。因此,如果 OP 的问题是将某些对共享/相同数据(状态)进行操作/作用的行为(方法)分解为可组合的重用单元(mixns/traits),那很好,为观众提供了一些示例代码。否则,@Manngo 的方法已经足够可行了。
  • @BlueJ774 ...当然,您可以提出一些示例代码,这些代码可以简化为您在这里所指的核心问题。这将使您更容易理解问题/您的问题并以更有帮助的方式回答。

标签: javascript oop types traits composition


【解决方案1】:

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


按照 OP 对所需特征的描述,可以很容易地选择 基于函数的 mixin/trait 方法。从而实现细粒度的可组合/可重用 每个单位都描述了一个特定的行为集,该行为集根据自己的行为和 不同的(封装的)数据。

可以实现某种flammableoneHanded 行为伴随 通过例如Weapon 基类。

但是从上述所有内容组成WoodenShortSword 并不像 就像人们乍一看可能期望的那样简单。可能有方法 来自oneHandedWeapon 需要对彼此采取行动(封装) 状态例如立即更新武器的isActivated 状态,例如 调用oneHandedtakeInLeftHand 方法,或者以防万一 武器的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; }

【讨论】:

  • 这是一个我没有考虑过的非常酷的方法。但是,如果您有一些易燃的东西呢?如果你没有在一个对象上运行 mixin(因为没有更好的词)withFlammable() 而你尝试做thing.isFlammable,你会得到undefined。我觉得必须应用假设的 notFlammable() 会变得非常麻烦,因为各种属性/属性加起来。
  • 如果您确实处理过不同的类型并且需要区分它们,那么除了基于“鸭子类型”的类型检测之外,别无他法。在相应地处理它之前,您必须询问一个对象的能力。我的示例只是展示了如何将行为和状态分解为更小的重用单元,然后在从这些构建块组装复合类型时处理冲突解决/方法修改,而现在您似乎正在明确地寻找类型检测。因此,正如我在 5 天前已经提出的那样,您应该考虑 @Mango 的解决方案。
【解决方案2】:

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-21
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2016-10-16
    • 2021-04-09
    相关资源
    最近更新 更多