How Do I Organize Data By Common Traits?
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?"