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 ...
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; }
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:
function doit(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?"