Skip to content Skip to sidebar Skip to footer

How Do I Organize Data By Common Traits?

I'm having trouble cataloging data in a way that allows me to reference data by its common descriptors or traits. I'm well aware of inheritance, traits (the programming concept),

Solution 1:

If @Manngo 's approach is not already the solution, one might consider giving this answer a 10 to 15 min read. It implements @Manngo 's approach but focuses on solving common composition conflicts if it comes to creation of composite types from stateful mixins/traits.


Following the OP's description of the desired traits, one easily could go for a function based mixin/trait approach. Thus implementing fine grained composable/reusable units that each describe a specific behavioral set that acts upon its own and distinct (encapsulated) data.

One would implement some kind of flammable and oneHanded behavior accompanied by e.g. a Weapon base class.

But composing a WoodenShortSword from all of the above mentioned is not as straightforward as one might expect at first sight. There might be methods from oneHanded and Weapon that need to take action on each others (encapsulated) state for e.g. updating a weapon's isActivated state as soon as an e.g. takeInLeftHand method of oneHanded gets invoked, or visa verce in case a weapon's deactivate action takes place. It then was nice getting updated the inner isInHand state of oneHanded.

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

A longer example code as proof of concept then might look like this one ...

functionwithFlammable() {                                // 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: functioncatchFire () {
      return (isInFlames = true);
    },
    enumerable: true
  });
  defineProperty(this, 'extinguish', {
    value: functionextinguish () {
      return (isInFlames = false);
    },
    enumerable: true
  });
}


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

    isInLeftHand = false,
    isInRightHand = false;

  functionisLeftHanded() {
    return (isInLeftHand && !isInRightHand);
  }
  functionisRightHanded() {
    return (isInRightHand && !isInLeftHand);
  }
  functionisInHand() {
    return (isInLeftHand || isInRightHand);
  }

  functionputFromHand() {
    returnisInHand() ? (isInLeftHand = isInRightHand = false) : (void0);
  }

  functiontakeInLeftHand() {
    return !isInLeftHand ? ((isInRightHand = false) || (isInLeftHand = true)) : (void0);
  }
  functiontakeInRightHand() {
    return !isInRightHand ? ((isInLeftHand = false) || (isInRightHand = true)) : (void0);
  }
  functiontakeInHand() {
    return !isInHand() ? takeInRightHand() : (void0);
  }

  functionswitchHand() {
    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
  });
}


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

  defineProperty(this, 'toString', {
    value: functiontoString () {
      returnJSON.stringify(this);
    },
    enumerable: true
  });
  defineProperty(this, 'valueOf', {
    value: functionvalueOf () {
      returnJSON.parse(this.toString());
    },
    enumerable: true
  });
}


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

    functionisActivated() {
      return isActivatedState;
    }

    functiondeactivate() {
      return isActivatedState ? (isActivatedState = false) : (void0);
    }
    functionactivate() {
      return !isActivatedState ? (isActivatedState = true) : (void0);
    }

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


classWoodenShortSwordextendsWeapon {                   // ... theconstructor() {                                         // inheritance// partsuper();                                              // ...

    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 = functiondeactivate () {            // "after returning" method modification.var
        returnValue = procedWithUnmodifiedDeactivate();

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

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

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

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

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

    this.takeInLeftHand = functiontakeInLeftHand () {    // "before" method modification.if (!this.isInHand) {
          procedWithUnmodifiedActivate();
      }
      returnprocedWithUnmodifiedTakeInLeftHand();
    };
    this.takeInRightHand = functiontakeInRightHand () {  // "before" method modification.if (!this.isInHand) {
          procedWithUnmodifiedActivate();
      }
      returnprocedWithUnmodifiedTakeInRightHand();
    };
  }
}


var
  sword = newWoodenShortSword;

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

Solution 2:

JavaScript objects are extensible, so that an expression such as thing.Flammable=true is valid and will work.

To test whether an object has a property, you can use thing.hasOwnProperty('property'). This is better than 'property in thing` because the latter will include the prototype chain.

A function could then work as follows:

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

This way an object can have multiple characteristics without bothering with faking multiple inheritance.

Post a Comment for "How Do I Organize Data By Common Traits?"