According to various sources (2ality, esdiscuss) one should be able to add mixins to classes:
EDIT discovered that class methods are not enumerable
As it has been discussed before by @mfeineis, @aadit-m-shah and @gotofritz as well as shown with the latest post from @ftor, one always needs to think carefully of witch class going to cripple into a "mixin" and than how to (re)assemble them into a hierarchy that still could be considered a good design. Such examples just show, that JavaScript might be in need for something that comes closer to traits than what we already (forced to) use instead. Something like ...
ApplicableType's apply time.In order to prove such a concept in practice I did refactor all of the above examples into composition of trait from trait and into object composition from traits inside class constructors.
I would like discussing such an approach, its advantages and its shortcomings in comparison with all of the already accepted techniques that are in use today.
// - stateful applicable object/type proxy that does
// link `this` context and injected local state.
//
const withExposeInternalStateProxy = Trait.create(function (use, applicator) {
applicator(function (stateValue) {
this.valueOf = function valueOf () {
return Object.assign({}, stateValue);
};
this.toString = function toString () {
return JSON.stringify(stateValue);
};
});
});
// - applicable object/type that just implements plain behavior.
// - also known as: function based mixin ... kind of "FLight Mixin reloaded".
//
const withHumanBehavior = Trait.create(function (use, applicator) {
function allowWeekness() {
return "allow weekness";
}
function showStrength() {
return "show strength";
}
function thinkFirst() {
return "think first";
}
function drinkingBeer() {
return "drinking beer";
}
applicator(function () {
this.allowWeekness = allowWeekness;
this.showStrength = showStrength;
this.thinkFirst = thinkFirst;
this.drinkingBeer = drinkingBeer;
});
});
// - composite trait that uses its trait composition language ... trait from trait(s).
//
const withStrongBeerDrinkingHabit = Trait.create(function (use, applicator) {
use(withHumanBehavior)
.apply(withHumanBehavior, "drinkingBeer").as("haveFun");
//.apply.all.without("thinkFirst");
//.apply(withHumanBehavior).without("thinkFirst");
});
// - trait that defines a specialized use case of another behavior.
//
const withDoAcceptAlreadyExistingDrinkingHabit = Trait.create(function (use, applicator) {
applicator(function () {
var
type = this;
if (typeof type.haveFun !== "function") {
withStrongBeerDrinkingHabit.call(type);
}
});
});
class CartoonCharacter {
constructor(stateValue) {
// linking `this` context and injected local state.
withExposeInternalStateProxy.call(this, stateValue);
}
drawnBy() {
return ("drawn by " + this.valueOf().author);
}
}
class MaleSimpsonsType extends CartoonCharacter {
constructor(stateValue) {
super(stateValue);
// object/type composition
//
withDoAcceptAlreadyExistingDrinkingHabit.call(super.constructor.prototype);
// withStrongBeerDrinkingHabit.call(super.constructor.prototype); // do not always apply.
//
// withStrongBeerDrinkingHabit.call(this); // nope ...prototype is the far better choice.
}
sayHey() {
return ("Hey, I am " + this.valueOf().name);
}
}
const homer = new MaleSimpsonsType({ name: "Homer Simpson", author: "Matt Groening" });
console.log("\n");
console.log('(homer + "") : ', (homer + ""));
console.log('homer.valueOf() : ', homer.valueOf());
console.log("\n");
console.log('homer.drawnBy() : ', homer.drawnBy());
console.log('homer.haveFun() : ', homer.haveFun());
console.log('homer.sayHey() : ', homer.sayHey());
console.log("\n");
console.log('Object.keys(homer) : ', Object.keys(homer));
console.log('homer.constructor : ', homer.constructor);
console.log('Object.keys(homer.constructor.prototype) : ', Object.keys(homer.constructor.prototype));
console.log('homer.constructor.prototype : ', homer.constructor.prototype);
console.log('(homer instanceof MaleSimpsonsType) : ', (homer instanceof MaleSimpsonsType));
console.log('(homer instanceof CartoonCharacter) : ', (homer instanceof CartoonCharacter));
.as-console-wrapper { max-height: 100%!important; top: 0; }
For backing up the above example, there is another thread - Refactoring legacy mixin-based class hierarchies - where I also took the chance of refactoring the OP's given example. This one does feature after and around modification of plain object based behavior. The same approach/toolset that was used here, did meet there all of the OP's requirements. One now is able of working with classes, gets encapsulated state/data for free and also has stateful support for any kind of method modification.