I am learning about compositions in Javascript. So I want to ask if this is correct way of doing things.
I made some exercises that look like this:
class Animal {
// constructor() {
// }
eat = () => {
console.log("this creature is eating");
}
}
const AnimalsWithWings = superclass => class extends superclass {
constructor(Feathers, ...args) {
super(...args);
Object.assign(this, { Feathers });
}
}
const CanDive = superclass => class extends superclass {
// constructor( ...args) {
// super(...args);
// }
dive = () => {
console.log("Can Dive");
}
}
class Duck extends AnimalsWithWings(CanDive(Animal)) {
constructor(eats, ...args) {
super(...args);
Object.assign(this, { eats });
}
}
const duffy = new Duck("watermelon", true);
console.log(duffy);
duffy.dive();
duffy.eat()
I am still in learning process so I just need some pointers.
Did it do more or less what you expected? Then, sure, it's a correct way to do it, whatever "correct" means here.
It looks to me like it does what it was meant to do when I pop it into the console. I can't really say much more on your code specifically because I'm not sure what concrete domain it's trying to model, aside from maybe breaking down Ducks into atomic pieces.
If you're going to do it this way, though, I'd personally prefer to use a params object instead of just changing the constructor signature like that with AnimalsWithWings
. That way, the order of extra parametrizations doesn't depend on the order in which the mixins were applied, which I would consider a Surprise. Surprises are bad.
const AnimalsWithWings = superclass => class extends superclass {
// Everyone receives the same `params` object.
// They only take what they know about, and ignore the rest.
constructor(params) {
super(params);
Object.assign(this, { Feathers: params.Feathers });
}
}
Even more personal opiniony, I'd name them WithDiving
and WithWings
instead, just to keep a somewhat consistent naming scheme, and to better imply that these are modifiers, not "real" base classes.
Your code does saddle every Duck with a prototype chain 4 prototypes long, but eh, whatever. If it somehow becomes a performance problem then you can create a utility function to optimize the mixin process or something. Flatten the prototypes, maybe.
Your code does also let you call super.method()
in methods, though it's debatable whether you should ever use that in a mixin at all. I'd say you shouldn't, unless you want your mixins to implicitly depend on each other, which is a Surprise.
There are plenty of other ways of doing mixins, too.
Object.assign()
when doing that flattening, if you want to properly handle things like get
/set
accessors, etc.)Object.create()
to create instances. (same thing about iterating property descriptors.)Object.create()
instead of iteratively extending base classes.