I'm not certain how I would implement this pattern to create varying auras.
One hase to make a decision, either designing an Aura
constructor-/factory-based
and not cutting off prototypal delegation in order of being able going later for
more aura variations making use of inheritance, or decomposing a fully-fledged
Aura
into various mixins, each of it with a well defined set of fine grained
behavior.
The aura instance based approach most propably gets complicated where it relies
on forwarding if one is going to "add" an aura instance (that in the given example
is capable of event dispatching) onto a character ... MyCharacter.addAura(new MyAura(any, args))
...
The addAura
method of a player's constructor needs to be implemented in a way
that already takes into account that aura by itself features event-target methods.
Thus this code has to come up with an own solution for forwarding in order to enable
a player of event dispatching for the time carrying an aura along.
The next given boiled down but working example(s) might point to what just was said ...
var Player = (function () { // sort of factory module implementation.
var
// ...
// ...
addAura = function (player, aura) { // simplified forwarding or delegation.
// ...
player.on = function (type, handler) {
aura.on(type, handler); // forwarding.
// please try switching it to delegation ...
//aura.on.apply(player, arguments); // delegation.
//aura.on.call(player, type, handler); // delegation.
console.log("player.on :: player.getName() ", player.getName());
};
// ...
},
Constructor = function Player (name) {
// ...
// ...
this.getName = function () {
return name;
};
//this.addAura = function (aura) {
// addAura(this, aura)
//};
},
createPlayer = function (config) { // factory.
var
// ...
//player = (new Constructor)
player = (new Constructor(config.name))
// ...
;
//player.name = config.name;
player.addAura = function (aura) {
addAura(player, aura)
};
return player;
}
;
return {
create: createPlayer
};
}());
...
var // simplified aura object - for demonstration only.
aura = {
on: function (type, handler) {
if (typeof handler == "function") {
handler.call(this);
}
console.log("aura :: on - [this, arguments] ", this, arguments);
}
},
p1 = Player.create({name:"p1"}), // 1st character/player.
p2 = Player.create({name:"p2"}) // 2nd character/player.
;
...
p1.addAura(aura);
p2.addAura(aura);
p1.on("tick", function () {console.log("tick :: this", this);});
p2.on("remove", function () {console.log("remove :: this", this);});
For all of the above, I personally always do prefer the function based
mixin composition approach.
The thing jumping out to me is that, unless I've misread, the events are being
enabled upon and applied to the Player class rather than the Aura object itself.
If one follows the approach of only dealing with player/character
instances whereas anything else that can be seen as set of additional
behaviors which a player can acquire or can get rid of (or that can be
awarded to or can be withdrawn from any player) at runtime, then for
the latter, one talks about role based composition. Thus both
Observable
and Aura
are mixins.
There will be no such thing like an aura object.
If a player does not already feature observable behavior, but an
aura behavior does rely on it, then the Aura
mixin should make
use of the former. A player's factory then does not deal with
Observable
but a player object will feature Observable
behavior
as soon as an Aura
mixin gets applied to it.
... and now coming back again to ...
I'm not certain how I would implement this pattern to create varying auras.
Sticking to function based mixins there are again mainly two solutions.
Firstly, as in the example I provided in my first answer, one can go for
a sole but configurable Mixin. Or secondly, one does favor the already
mentioned many aura mixin fragments.
var $rpg = { // mocking and faking heavily.
Event: {
enable: function (observable) {
observable.on = function (type, handler) {
if (typeof handler == "function") {
handler.call(this);
}
console.log("aura :: on - [this, arguments] ", this, arguments);
};
return observable;
}
}
};
...
var withAuraBase = function AuraBaseMixin (group) { // not a constructor but a simple function based "flight mixin".
var auraEnabled = this;
// ...
$rpg.Event.enable(auraEnabled);
// ...
auraEnabled.group = group;
// ...
};
var withFencinessFactorAura = function FencinessFactorAuraMixin (fencinessFactor) {
// ...
this.fencinessFactor = fencinessFactor;
// ...
};
var withCheatingFeaturesAura = function CheatingFeaturesAuraMixin (config) {
// ...
this.cheatingFeatures = config;
// ...
};
The Player
factory code from above then will shrink to ...
var Player = (function () { // sort of factory module implementation.
var
// ...
// ...
Constructor = function Player (name) {
// ...
// ...
this.getName = function () {
return name;
};
},
createPlayer = function (config) { // factory.
var
// ...
player = (new Constructor(config.name))
// ...
;
//player.name = config.name;
return player;
}
;
return {
create: createPlayer
};
}());
...
var
p1 = Player.create({name:"player1"}), // 1st character/player.
p2 = Player.create({name:"player2"}) // 2nd character/player.
;
// applying mixin examples differently.
withAuraBase.call(p1, "classification");
withAuraBase.call(p2, "classification");
withFencinessFactorAura.call(p1, 10);
withCheatingFeaturesAura.call(p2, {/*cheatingFeaturesConfig*/});
console.log("p1.getName(), p1", p1.getName(), p1);
console.log("p2.getName(), p2", p2.getName(), p2);
p1.on("tick", function () {console.log("tick :: this", this);});
p2.on("remove", function () {console.log("remove :: this", this);});
Hint: "meta" mixins also can be easily constructed from smaller ones.
Thus enabling the compositions of tailored behavioral sets.
var withFencyAura = function FencyAurMixin (group, fencinessFactor) {
var auraEnabled = this;
withAuraBase.call(auraEnabled, group);
withFencinessFactorAura.call(auraEnabled, fencinessFactor);
};
var withCheatingAura = function CheatingAuraMixin (group, config) {
var auraEnabled = this;
withAuraBase.call(auraEnabled, group);
withCheatingFeaturesAura.call(auraEnabled, config);
};
Note: In practice I mostly end up with configurable mixins, whereas the above given
example of composing mixins from other mixins seems to be a rare case.
_events
member on the object (whenon
is called for instance). That's the approach taken by asEvented. – VealAura = eventMixin.mixInto(Aura);
where this applies the mixin and returns a new composed constructor. The only thing with that is the mixin application cannot be deferred since no one should be referencing the old constructor at this point. – VealnewMixin
but mixin usage becomes very simple and the same goes for declaring new mixin types. – Veal