How to organize data by common features?

I am having trouble cataloging data in such a way that I can refer to data by its common descriptors or traits. I am knowledgeable about inheritance, traits (programming concept) and interfaces, but none of them are the correct answer to my problem.

I am writing a JavaScript program that has potentially many different items or objects. Let's say I have a data type WoodenShortSword

and I want to say that it has traits Flammable

and Weapon

and OneHanded

. Then I want to define a function that accepts only objects that are both as an argument OneHanded

, and Weapon

. Or perhaps only objects that are Flammable

and Wearable

, or Flammable

, and not a Weapon

.

How can i do this?

So far, I've looked at inheritance in JavaScript and TypeScript, which would technically work, but it would require a lot of intermediate classes, since multiple inheritance is not allowed. Like FlammableWeapon

or OneHandedWeapon

. This is cumbersome and not ideal.

I've looked at TypeScript abstract classes and interfaces, but they are more about exchanging functions rather than describing things. And there is no built-in way I can test to check if an object satisfies an interface at runtime.

I also looked at the tcomb library . While the system as I describe it is possible, it is still very cumbersome and error prone.

+3


source to share


2 answers


If @ Manngo's approach is not yet a solution, one might consider giving this answer readable in 10-15 minutes. It implements @ Manngo's approach, but focuses on resolving common composition conflicts when it comes to creating composite types from target / feature states.


By following the OP's description of the desired traits, a function-based mixin / trait method can be easily found. Thus, the use of fine-grained composite / reusable units, each of which describes a specific behavioral set that acts independently and (encapsulated) data.

Some behavior would be implemented flammable

and oneHanded

eg. a Weapon

base class.

But compiling WoodenShortSword

all of the above is not what one would expect at first glance. There can be methods from oneHanded

and Weapon

that must take action on each other (encapsulated) state, for example. updating the weapon isActivated

as soon as, for example, the takeInLeftHand

method oneHanded

is called or the visa is correct in the event the weapon action occurs deactivate

. Then it was nice to receive updates to the inner state isInHand

oneHanded

.



A reliable approach for this is method modification, which should rely on boilerplate code, unless JavaScript implements one day Function.prototype[around|before|after|afterReturning|afterThrowing|afterFinally]

.

Longer example code as a proof of concept might look like this ...

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; }
      

Run codeHide result


+1


source


JavaScript objects are extensible, so an expression such as thing.Flammable=true

is valid and will work.

To check if an object has a property you can use thing.hasOwnProperty('property')

. It's better than 'property

things because the latter will involve a prototype chain.

Then the function could work like this:



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

      

This way an object can have multiple characteristics without worrying about forging multiple inheritance.

0


source







All Articles