6
votes

I'm trying to follow along with a C# design patterns book by writing my code in TypeScript. Perhaps this is my first mistake, but it's a way I enjoy to learn a language.

TypeScript doesn't support the abstract keyword for classes, so I am trying to simulate it. Maybe this is my second mistake.

Here is my interface and classes:

interface IEngine {
  getSize(): number;
  getTurbo(): boolean;
}

class AbstractEngine implements IEngine {
  constructor(private size: number, private turbo: boolean) {
    throw "Abstract class";
  }

  public getSize(): number {
    return this.size;
  }

  public getTurbo(): boolean {
    return this.turbo;
  }

  public toString(): string {
    var funcNameRegex = /function (.{1,})\(/;
    var results = (funcNameRegex).exec(this.constructor.toString());
    var className = (results && results.length > 1) ? results[1] : '';
    return className + " (" + this.size + ")";
  }
}

class StandardEngine extends AbstractEngine {
  constructor(size: number) {
    // not turbo charged
    super(size, false);
  }
}

When trying to instantiate an AbstractEngine with new AbstractEngine(1, true) I get an "Abstract class" error as expected.

When trying to instantiate a StandardEngine with new StandardEngine(9000) I also get an "Abstract class" error.

Is there a way I can simulate an abstract class in TypeScript, have it unable to be instantiated, but still call super in a class that extends it? And what about simulating abstract methods, can I protect those and still call the super method?

4

4 Answers

5
votes

As of today, TypeScript 1.6 is live and has support for the abstract keyword.

abstract class A {
  foo(): number { return this.bar(); }
  abstract bar(): number;
}

var a = new A();  // error, Cannot create an instance of the abstract class 'A'

class B extends A {
  bar() { return 1; }
}

var b = new b();  // success, all abstracts are defined
1
votes

I advise you not to do that. When the TypeScript compiler will implement a mechanism for abstract function, it is time to use it. But hacks that work at runtime are incomprehensible and degrade performance.

The interfaces are the great strength of TypeScript. They should be used massively.

Your example should be written like this:

interface Engine {
  getSize(): number;
  getTurbo(): boolean;
}

class StandardEngine implements Engine {
  constructor(private size: number, private turbo: boolean) {
  }
  public getSize(): number {
    return this.size;
  }
  public getTurbo(): boolean {
    return this.turbo;
  }
}

The simplest solution is often the best.

If you want to reuse code without a parent class which would then necessarily usable, the Handbook suggests Mixins. Mixins are a way of coping skills from several distinct entities.

Or with modules it is possible to keep private implementation (and therefore organize it as you want it) and export only interfaces and factories. An example:

module MyEngineModule {
  export interface Engine {
    getSize(): number;
    getTurbo(): boolean;
  }
  export interface StandardEngine extends Engine {
  }
  export function makeStandardEngine(size: number, turbo: boolean): StandardEngine {
    return new ImplStandardEngine(size, turbo);
  }
  // here classes are private and can inherit or use mixins…
  class ImplEngine {
    constructor(private size: number, private turbo: boolean) {
    }
    public getSize(): number {
      return this.size;
    }
    public getTurbo(): boolean {
      return this.turbo;
    }
  }
  class ImplStandardEngine extends ImplEngine implements StandardEngine {
  }
}
console.log(MyEngineModule.makeStandardEngine(123, true).getSize());
0
votes

When calling the StandardEngine constructor, you have a call to super(size, false). This call into the base class is what is generating the second "Abstract class" error.

To simulate an abstract base class that will throw when instantiated, create an Init function that is called from your derived class.

class AbstractEngine implements IEngine {
  private _size: number;
  private _turbo: boolean;
  constructor() {
    throw "Abstract class";
  }
  init(size:number, turbo: boolean) {
    this._size = size;
    this._turbo = turbo;
  }
}

class StandardEngine extends AbstractEngine {
  constructor(size: number) {
    // not turbo charged
    // do not call super here
    init(size, false);
  }
}
0
votes

An alternative solution would be to user a property that if set indicates that the constructor is being called from a child class it is safe to continue. This is shown below :

class AbstractEngine {
  safe; // IMPORTANT : do not initialize
  constructor(private size: number, private turbo: boolean) {
    if(!this.safe) throw "Abstract class"; // IMPORTANT
  }    

}

class StandardEngine extends AbstractEngine {  
  constructor(size: number) {       
    this.safe = true; // IMPORTANT
    super(size, false);
  }
}