網站搜尋

OOP - 我如何模擬一個抽象類別並呼叫它的建構子?


我正在嘗試透過在 TypeScript 中編寫代碼來跟隨一本 C# 設計模式的書。也許這是我的第一次錯誤,但這是我喜歡學習一門語言的方式。

TypeScript 不支持類別的 abstract 關鍵字,所以我正在嘗試模擬它。也許這是我的第二個錯誤。

這是我的介面和類別:

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);
  }
}

當嘗試使用 new AbstractEngine(1, true) 實例化 AbstractEngine 時,我如預期地得到了「抽象類」錯誤。

當嘗試使用 new StandardEngine(9000) 實例化 StandardEngine 時,我也收到“抽象類別”錯誤。

有辦法在 TypeScript 中模擬抽象類別,使其無法被實例化,但仍然能在擴展它的類別中調用 super 嗎?那模擬抽象方法呢,我能保護這些方法並仍然調用 super 方法嗎?

答案#1

截至今天,TypeScript 1.6 已經上線,並且支援 abstract 關鍵字。

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

答案#2

我建議你不要那麼做。當 TypeScript 編譯器實現抽象函數的機制時,才是使用它的時候。但在運行時有效的黑客方法是不可理解的,並且會降低性能。

介面是 TypeScript 的最大優勢。它們應該被大量使用。

你的範例應該這樣寫:

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;
  }
}

最簡單的解決方案通常是最好的。

如果你想在沒有父類別的情況下重複使用程式碼,而這樣父類別就不一定能被使用,《手冊》建議使用混入(Mixins)。混入是一種從幾個不同實體中獲取技能的方法。

或者使用模組,可以保持私有實作(因此可以按你想的方式組織它)並且只匯出介面和工廠。舉個例子:

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());