Mixins Flashcards

1
Q

What is a mixin ?

A

A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.

To dig deeper into the implications of this definition, let’s add two terms to our mixin lexicon:

  • mixin definition: The definition of a class that may be applied to different superclasses.
  • mixin application: The application of a mixin definition to a specific superclass, producing a new subclass.

The mixin definition is really a subclass factory, parameterized by the superclass, which produces mixin applications. A mixin application sits in the inheritance hierarchy between the subclass and superclass.

Gilad Bracha and William Cook, Mixin-based Inheritance. Retrieved July 7, 2023.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How are traditional mixins done?

A
// Each mixin is a traditional ES class
class Jumpable {
  jump() {}
}
 
class Duckable {
  duck() {}
}
 
// 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, Duckable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);
 
let player = new Sprite();
player.jump();
console.log(player.x, player.y);
 
// 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)
      );
    });
  });
}

A version of this has even made it into JavaScript as Object.assign.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is the problem with traditional mixins?

A

Simply copying properties into a target object has a few issues.

  • Prototypes are modified in place. The prototypes are directly mutated. This is a problem if the prototype is used anywhere else that the mixed-in properties are not wanted. Mixins that add state can create slower objects in VMs that try to understand the shape of objects at allocation time. It also goes against the idea that a mixin application should create a new class by composing existing ones.
  • super doesn’t work. A mixin’s methods should be able to delegate to an overridden method up the prototype chain. This won’t work with copying functions.
  • Incorrect precedence. By overwriting properties, mixin methods take precedence over those in the subclass. They should only take precedence over methods in the superclass, allowing the subclass to override methods on the mixin.
  • Composition is compromised. Mixins often need to delegate to other mixins or objects on the prototype chain, but there’s no natural way to do this with traditional mixins. Since functions are copied onto objects, naive implementations overwrite existing methods.

References to functions are duplicated across all applications of a mixin, where in many cases they could be bundled in a shared prototype. By overwriting properties, the structure of protytpes and some of the dynamic nature of JavaScript is reduced: you can’t easily introspect the mixins or remove or re-order mixins, because the mixin has been expanded directly into the target object.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How can you create a mixin subclass factory?

A

We rely on two features of JavaScript classes:

class can be used as an expression as well as a statement. As an expression it returns a new class each time it’s evaluated. (sort of like a factory!)

The extends clause accepts arbitrary expressions that return classes or constructors.

The key is that classes in JavaScript are first-class: they are values that can be passed to and returned from functions.

All we need to define a mixin is a function that accepts a superclass and creates a new subclass from it, like this:

let MyMixin = (superclass) => class extends superclass {
  foo() {
    console.log('foo from MyMixin');
  }
};

Then we can use it in an extends clause like this:

class MyClass extends MyMixin(MyBaseClass) {
  /* ... */
}

And MyClass now has a foo method via mixin inheritance:

let c = new MyClass();
c.foo(); // prints "foo from MyMixin"
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

How can you apply multiple mixin fatories to a single class?

A
class MyClass extends Mixin1(Mixin2(MyBaseClass)) {
  /* ... */
}

Mixins can easily inherit from other mixins by passing the superclass along:

let Mixin2 = (superclass) => class extends Mixin1(superclass) {
  /* Add or override methods here */
}

And you can use normal function composition to compose mixins:

let CompoundMixin = (superclass) => Mixin2(Mixin3(superclass));
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Can you use decorators to provide mixins in TypeScript?

A

No, you cannot use decorators to provide mixins via code flow analysis

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What are static property mixins in TypeScript and how can they be used?

A

In TypeScript, a static property mixin is a way to create classes that encapsulate behavior that can be reused in other classes, specifically for static properties. This pattern uses functions that return classes based on a generic type.

This is particularly useful when you want to have different types for the static properties of different instances of the class. By default, TypeScript treats classes as singletons for the purpose of static properties, meaning all instances share the same static property.

Here’s an example:

function base<T>() {
  class Base {
    static prop: T;
  }
  return Base;
}
 
function derived<T>() {
  class Derived extends base<T>() {
    static anotherProp: T;
  }
  return Derived;
}
 
class Spec extends derived<string>() {}
 
Spec.prop; // string
Spec.anotherProp; // string

In this example, base and derived are functions that return classes. These returned classes have static properties that depend on a generic type T. The Spec class extends derived<string>(), meaning that the static properties prop and anotherProp are both of type string.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly