Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Review Mixins for language changes #15

Open
RyanCavanaugh opened this issue Apr 10, 2015 · 3 comments
Open

Review Mixins for language changes #15

RyanCavanaugh opened this issue Apr 10, 2015 · 3 comments
Labels

Comments

@RyanCavanaugh
Copy link
Member

Update this file so that it is up-to-date with regard to new language features, especially around union types and ES6 features

@DanielRosenwasser
Copy link
Member

This could probably use union types, and potentially local types.

@DustinWehr
Copy link

I took some time (too much) to refine the Mixin pattern I've been using, with the intention of offering to write a new version of http://www.typescriptlang.org/Handbook#mixins if you guys like it. I think it's a good use of user-defined type guard functions.
I'll give a short summary, then some items of differences between the new pattern and the Handbook's, and then the example from the Handbook rewritten in terms of the new pattern. I have no opinion about naming conventions, so please do suggest alternatives.

For the summary I'll use the example as a reference. The new pattern produces a namespace ActivatableMixin with three visible members:
-An interface, ActivatableInterface
-A function usedBy, which tests if an object's class uses the mixin. This is the main improvement over the Handbook pattern.
-A function mixInto, such that ActivatableMixin.mixInto(C) does everything that the Handbook's applyMixins(C, [Activatable]) does, and additionally stores the class C, so we can later use (x instanceof C) in ActivatableMixin.usedBy.

In addition to the usedBy function, I'm suggesting two small alterations to prevent some surprising behavior of the current Handbook pattern that bit me.

  1. Added an option to exclude "constructor" from the mixed-in property names. In the code below (see makeMixinApplicator) it defaults to true. When it's excluded, you have smartObject.constructor === SmartObject instead of smartObject.constructor === <constructor of last-supplied mixin>, which I think is less surprising, at least for someone like me who didn't know the JS prototype system well when first learning TS.
  2. The implementation of the mixin is hidden. This is so that a new TS user doesn't mistakenly think they can use instanceof to check if an object implements a mixin. It's an easy mistake to make since smartObject instanceof Activatable compiles fine.
namespace DisposableMixin {
  var classesUsingThis : Function[] = [];

  export interface DisposableInterface {
    isDisposed: boolean;
    dispose() : void;
  }

  class DisposableImplementation implements DisposableInterface {
    isDisposed:boolean;
    dispose():void {
      this.isDisposed = true;
    }
  }

  export var mixInto = makeMixinApplicator(DisposableImplementation, classesUsingThis);
  export var usedBy = makeMixinInstanceChecker<DisposableInterface>(classesUsingThis);
}


namespace ActivatableMixin {
  var classesUsingThis : Function[] = [];

  export interface ActivatableInterface {
    isActive: boolean
    activate() : void
    deactivate() : void
  }

  class ActivatableImplementation implements ActivatableInterface {
    isActive: boolean;
    activate() {
      this.isActive = true;
    }
    deactivate() {
      this.isActive = false;
    }
  }

  export var mixInto = makeMixinApplicator(ActivatableImplementation, classesUsingThis);
  export var isActivatable = makeMixinInstanceChecker<ActivatableInterface>(classesUsingThis);
}

import DisposableInterface = DisposableMixin.DisposableInterface;
import ActivatableInterface = ActivatableMixin.ActivatableInterface;

class SmartObject implements DisposableInterface, ActivatableInterface {
  constructor() {
    setInterval(() => console.log("activated: " + this.isActive + " | disposed: " + this.isDisposed), 500);
  }

  interact() {
    this.activate();
  }

  // Disposable
  isDisposed: boolean = false;
  dispose: () => void;

  // Activatable
  isActive: boolean = false;
  activate: () => void;
  deactivate: () => void;
}
DisposableMixin.mixInto(SmartObject);
ActivatableMixin.mixInto(SmartObject);

var smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);


////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

function makeMixinApplicator(mixinImplementation:any, classesUsingMixin:any[], exclude_constructor=true) : (c:any) => void {
  return (c:any) => {
    classesUsingMixin.push(c);
    Object.getOwnPropertyNames(mixinImplementation.prototype).forEach(name => {
      if(!exclude_constructor || name !== "constructor") {
        (<any>c.prototype)[name] = (<any>mixinImplementation.prototype)[name];
      }
    })
  };
}

function makeMixinInstanceChecker<T>(classesUsingMixin:Function[]) : ( (x:any) => x is T ) {
  return (x:any) : x is T => {
    for(let i=0; i<classesUsingMixin.length; i++) {
      if(x instanceof classesUsingMixin[i])
        return true;
    }
    return false;
  }
}

@ghost
Copy link

ghost commented Feb 16, 2018

We should include documentation like https://blog.mariusschulz.com/2017/05/26/typescript-2-2-mixin-classes -- our current documentation makes no mention of being able to do that.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

3 participants