2

I don't understand TypeScript's article on Alternative Pattern for mixins. I see that when I execute the code as they have on the website, I get the methods mixed in, but I don't get properties. e.g. this example

I added the property z = 1; to Jumpable, create my class, and console.log z, but the value is undefined. If instead I used the Constrained Mixins pattern, it works as expected, but I don't particularly care for how that looks and was hoping that I'm doing something wrong with the Alternative Pattern. Does anyone have any guidance?

Code for reference:

// Code copied from https://www.typescriptlang.org/docs/handbook/mixins.html#alternative-pattern

// Each mixin is a traditional ES class
class Jumpable {
  z = 1;
  jump() {
    console.log('Jumped');
  }
}

// Including the base
class Sprite {
  x = 0;
  y = 0;
}

// Then you create an interface which merges
// the expected mixins with the same name as your base
interface Sprite extends Jumpable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable]);

let player = new Sprite();
console.log(player.z);

// This can live anywhere in your codebase:
function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}
5
  • 1
    In my opinion the biggest design flaw is the (mis)use of classes as mixins. Classes are perfect for instantiation, inherited properties/behavior and subtyping. They are not best suited for pure (mixin based) composition tasks. There are better alternatives for (de)structuring code into composable units of reuse. Commented May 19, 2021 at 9:20
  • Answer ... the provided code and especially the implementation of applyMixins clearly shows that at no point a (necessary super) Jumpable call is involved. Everything is just about copying properties from one prototype to another. Thus one achieves gluing together prototypal behavior but not the composition of properties which materialize just at instantiation/call time. For the OP's particular use case one was better off with a Jumper class and class Sprite extends Jumper { /* ... */}. Commented May 19, 2021 at 12:16
  • 1
    Conclusion ... The Alternative Pattern works very well as long as one just wants to copy class specific behavior from one constructor's prototype to another constructor's prototype. Commented May 19, 2021 at 12:24
  • @PeterSeliger, feel free to post that as an answer, so I can upvote you properly. Thank you for the response though! Commented May 19, 2021 at 14:35
  • 1
    it's ok like it is now; I will upvote your research effort instead and Bergi's answer since it is more comprehensive and also does provide example code. Commented May 19, 2021 at 15:28

2 Answers 2

2

This pattern does not take property initialisation into account, i.e. it ignores the constructors of the mixins. This is why mixins should not have constructors at all, and ideally not use class syntax. (Notice that z = 1; is not a class property, it's syntactic sugar to create a .z instance property in the constructor).

To use this pattern, provide a mixin-specific initialisation method instead:

class Jumpable {
  z: number;
  initJumpable() {
    this.z = 1;
  }
  jump() {
    console.log('Jumped');
  }
}

class Sprite {
  x = 0;
  y = 0;
  constructor() {
    this.initJumpable()
  }
}

interface Sprite extends Jumpable {}

applyMixins(Sprite, [Jumpable]);
Sign up to request clarification or add additional context in comments.

8 Comments

So you're saying still use the applyMixins method as well as the init method you provided? If so, do you recommend the Constrained Mixins? Because they still use classes
@incutonez ... 1/2 ... Deside yourself by answering e.g. the question which will be asked in the very end. From the Alternative Pattern's provided example code and executing interface Sprite extends Jumpable, Duckable {}, applyMixins(Sprite, [Jumpable, Duckable]); and let player = new Sprite(); what do you expect Object.getPrototypeOf(player) to be? And how about applyMixins(Sprite, [Duckable, Jumpable]);? What is the prototype in the latter case?.. first: Duckable, last: Jumpable ...
@incutonez ... 2/2 ... From a mixin I personally don't expect either. In my opinion any mixin approach might change whichever kind of object including the prototype but it should never alter the prototype chain itself; thats exclusive to inheritance. Now the question. Can you personally or the project live with this kind of mixin implementation which is based on a crippled form of inheritance but tries to obfuscate this fact?
@incutonez Yes, this approach would still call applyMixins, to get the initJumpable method onto Sprite.prototype. Sorry if that wasn't clear - edited. Whether this pattern is a good approach I cannot say.
@incutonez Personally I would prefer class Sprite extends mixins(Duckable, Jumpable) { … } or class Sprite implements Duckable, Jumpable { constructor() { mixinDuckable(this); mixinJumpable(this); } … } but I've never really used mixins myself (in TypeScript).
|
1

For posterity, as of 5/19/2021, it seems like mixins are not well-supported in JS/TS (Bryntum seems to agree). There's the ts-mixer package which should most likely solve my problem, but I first decided to implement using Constrained Mixins. This route seems decent, except I don't think generics work well (Fiddle)... although, I could be totally doing something wrong. There was also the issue of trying to use {} as a type, and the eslint parser throwing a tantrum, but per this thread, it seems like disabling the rule is fine. All in all, this gets the job done, and I'm able to contain it in its own file, so I'm happy with this solution.

2 Comments

"... it seems like mixins are not well-supported in JS/TS ..." ... nitpicking ... mixins / traits / talents are natural to JS in many ways / flavors / schools ... the concept gets screwed by any approach which oppressively tries pressing it into classes.
That's fair, and I guess what I'm probably asking is more of multiple inheritance, at least with the desire to classify them... I have more of a problem with how mixins look. Defining a class is much cleaner.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.