【问题标题】:How do I simulate an abstract class and call its constructor?如何模拟一个抽象类并调用它的构造函数?
【发布时间】:2014-07-18 04:16:05
【问题描述】:

我正在尝试通过使用 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?那么模拟抽象方法呢,我可以保护它们并且仍然调用超级方法吗?

【问题讨论】:

    标签: oop design-patterns typescript


    【解决方案1】:

    截至今天,TypeScript 1.6 is live 并支持 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 编译器将实现抽象函数机制时,就该使用它了。但是在运行时起作用的 hack 难以理解并且会降低性能。

      接口是 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;
        }
      }
      

      最简单的解决方案往往是最好的。

      如果您想在没有父类的情况下重用代码,那么这将是必然可用的,Handbook suggests Mixins。 Mixin 是一种应对来自多个不同实体的技能的方式。

      或者使用模块可以保留私有实现(因此可以根据需要对其进行组织)并仅导出接口和工厂。一个例子:

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

      【讨论】:

      • 但是我需要能够在几个不同的具体类之间共享 AbstractEngine 中的实现,而不仅仅是 StandardEngine。抱歉,我没有在帖子中说明这一点。我看不出您的解决方案如何帮助实现这一目标。 edit 啊,没关系,我看到你提到了关于 mixins 的部分,它将完成在多个具体类之间共享代码。我得试试看。
      • 在使用 mixins 实现之后,我有点失望,因为我必须将大量代码复制到 StandardEngine 和 TurboEngine 类。我希望有一个关键字可以解决这个问题并消除调用 applyMixins 或抽象类或两者的必要性。
      • 此外,Engine 上的自定义 toString 方法在实现为 mixin 时总是为类名打印“Engine”,而不是像使用继承时那样打印“StandardEngine”或“TurboEngine”。
      • 我同意,mixin 缺乏优雅。它们是原型中的手动操作,它不是 DRY。我建议您使用最后一个解决方案。在一个模块中,使用继承创建私有类。例如 ImplEngine ← ImplStandardEngine。从模块外部,不会实例化 ImplEngine。
      【解决方案3】:

      拨打StandardEngine constructor 时,您可以拨打super(size, false)。这个对基类的调用是产生第二个“抽象类”错误的原因。

      要模拟实例化时将抛出的抽象基类,请创建一个从派生类调用的 Init 函数。

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

      【讨论】:

      • 如果我在 StandardEngine 中省略了对 super 的调用,我会收到错误“派生类的构造函数必须包含 'super' 调用”。在尝试您的代码时,我还收到错误“找不到符号'init'”,因为它缺少“this”限定符。
      【解决方案4】:

      另一种解决方案是使用一个属性,如果设置表明构造函数是从子类调用的,则可以安全地继续。如下图所示:

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

      【讨论】:

      • 当我尝试这段代码时,在实例化 StandardEngine 时仍然收到抽象类抛出的错误。我理解这个想法,但实现似乎不起作用。
      • @brycedarling 我修复了错误。基类不应设置该值,否则子值将被覆盖:)
      猜你喜欢
      • 2012-04-28
      • 1970-01-01
      • 2017-08-25
      • 1970-01-01
      • 2018-02-27
      • 2018-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多