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

时间:2017-04-14 01:52:00

标签: javascript oop types traits composition

我在以一种允许我通过其共同描述符或特征引用数据的方式编目数据时遇到了麻烦。我非常了解继承,特征(编程概念)和接口,但这些似乎都不是我问题的正确答案。

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

我该怎么做?

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

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

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

2 个答案:

答案 0 :(得分:1)

如果@Manngo的方法还不是解决方案,那么可以考虑给予 这个回答是10到15分钟的阅读。它实现了@Manngo的方法但重点突出 如果涉及复合材料的创建,解决常见的构图冲突 来自有状​​态混合/特征的类型。

根据OP对所需特征的描述,可以轻松地找到 基于函数的mixin / trait方法。从而实现细粒度可组合/可重复使用 每个单元描述一个特定的行为集,它自己和它自己的行为 不同的(封装的)数据。

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

但是,从上面提到的所有内容中编写WoodenShortSword并不如此 人们可能会一见钟情直截了当。可能有方法 来自oneHandedWeapon需要对彼此采取行动(封装) 例如,国家尽快更新武器的isActivatedtakeInLeftHand的{​​{1}}方法被调用,或签署verce以防万一 武器的oneHanded动作发生了。很高兴得到更新 deactivate的内部isInHand状态。

可靠的方法是方法修改必须依赖 在样板代码上,除非JavaScript在某一天本地实现 oneHanded

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

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');

答案 1 :(得分:0)

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

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

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

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

这样,对象可以具有多个特征,而不会因伪造多重继承而烦恼。