与构造签名的接口如何工作?

时间:2012-11-15 22:03:37

标签: typescript constructor interface

我在解决界面中定义构造函数的工作方式时遇到了一些麻烦。我可能完全误解了一些事情。但是我已经寻找了很长一段时间的答案,我找不到与此相关的任何内容。

如何在TypeScript类中实现以下接口:

interface MyInterface {
    new ( ... ) : MyInterface;
}

Anders Hejlsberg在此video中创建了一个包含类似内容的界面(大约14分钟)。但是对于我的生活,我无法在课堂上实现这一点。

我可能误解了一些事情,我没有得到什么?

编辑:

澄清。使用" new(...)"我的意思是"什么"。我的问题是我无法得到这个工作的最基本版本:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

这不是为我编译的,我得到了" Class' test'声明界面' MyInterface'但没有实现它:键入' MyInterface'需要构造签名,但键入' test'缺少一个"当试图编译它时。

编辑:

因此,在对这些反馈进行研究之后。

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () => test { return this; }
}

无效的TypeScript,但这并不能解决问题。您无法定义构造函数的返回类型。它将返回" test"。以下签名:     班级考试{         构造函数(){}     } 似乎是" new()=>测试" (通过将#34;类"悬停在在线编辑器中,仅粘贴该代码获得)。这就是我们想要的和我认为的。

任何人都可以在实际编译时提供此类或类似的示例吗?

编辑(再次......):

所以我可能想出了为什么可以在接口中定义它但不能在TypeScript类中实现的想法。以下工作:

var MyClass = (function () {
    function MyClass() { }
    return MyClass;
})();

interface MyInterface {
    new () : MyInterface;
}

var testFunction = (foo: MyInterface) : void =>  { }
var bar = new MyClass();
testFunction(bar);

这只是TypeScript的一个功能,可以让你连接javascript吗?或者是否可以在TypeScript中实现它而无需使用javascript实现类?

7 个答案:

答案 0 :(得分:127)

接口中的构造签名不能在类中实现;它们仅用于定义定义“新”功能的现有JS API。这是一个涉及接口new签名的示例:

interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;
}

class MadeFromString implements ComesFromString {
    constructor (public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable) {
    return new n('hello!');
}

console.log(makeObj(MadeFromString).name);

这为您使用以下内容调用makeObj的内容创建了一个实际约束:

class Other implements ComesFromString {
    constructor (public name: string, count: number) {
    }
}

makeObj(Other); // Error! Other's constructor doesn't match StringConstructable

答案 1 :(得分:52)

在我搜索完全相同的问题时,我去了解TypeScript-Team是如何做到的......

他们正在声明一个接口,然后是一个名称与接口名称完全匹配的变量。这也是键入静态函数的方法。

来自lib.d.ts的示例:

interface Object {
    toString(): string;
    toLocaleString(): string;
    // ... rest ...
}
declare var Object: {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ... rest ...
}

我试过了,它就像魅力一样。

答案 2 :(得分:4)

具有构造签名的接口并不意味着可以由任何类实现(乍一看,这对于像我这样具有C#/ Java背景的人来说可能看起来很奇怪但是给它一个机会)。它略有不同。

暂时将其视为具有调用签名的接口(如Java世界中的@FunctionalInterface)。它的目的是描述一个函数类型。所描述的签名应该由函数对象来满足......但不仅仅是任何高级函数或方法。它应该是一个知道如何构造对象的函数,一个在使用new关键字时被调用的函数。

因此,带有构造签名的接口定义了构造函数的签名!您的类的构造函数应该符合接口中定义的签名(将其视为构造函数实现接口)。它就像一个建筑工人或工厂!

以下是一段简短的代码,试图演示最常见的用法:

interface ClassicInterface { // old school interface like in C#/Java
    method1();
    ...
    methodN();
}

interface Builder { //knows how to construct an object
    // NOTE: pay attention to the return type
    new (myNumberParam: number, myStringParam: string): ClassicInterface
}

class MyImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

class MyOtherImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Builder
    constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Builder, myNumberParam: number, myStringParam: string): ClassicInterface {
    return new ctor(myNumberParam, myStringParam);
}

// And this is how we do it
let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");
let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");

答案 3 :(得分:3)

从设计角度来看,通常不在接口中指定构造函数要求。界面应描述您可以对对象执行的操作。如果需要,应允许实现接口的不同类需要不同的构造函数参数。

例如,如果我有一个界面:

interface ISimplePersistence {
    load(id: number) : string;
    save(id: number, data: string): void;
}

我可能有将数据存储为cookie的实现,它不需要构造函数参数,以及将数据存储在数据库中的版本,需要连接字符串作为构造函数参数。

如果你仍然想在一个接口中定义构造函数,那么有一种肮脏的方法可以解决这个问题:

Interfaces with construct signatures not type checking

答案 4 :(得分:1)

要实现预期的行为,您可以使用Decorators,即使这可能不是它们应该用于的目的。

interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

编译没有问题。相比之下,TestClass的以下定义

// error TS2345: Argument of type 'typeof TestClass' is not assignable to parameter of type 'MyInterface'.
@MyInterfaceDecorator
class TestClass {
    constructor (arg: string) { }
}

不会编译。

答案 5 :(得分:1)

要在Nils'answer上进行扩展,您还可以使用相同的技术制作通用的new功能:

interface MyArrayConstructor {
    <T>(...elements: Array<T>): MyArrayInstance<T>
    new <T> (...elements: Array<T>): MyArrayInstance<T>
}

// “Typecast” not the function itself, but another symbol,
// so that the body of myArray will also benefit from
// type-checking:
export const MyArray = myArray as MyArrayConstructor

interface MyArrayInstance<T> {
    push(...args: Array<T>): number
    slice(from?: number, to?:number): Array<T>
}

function myArray(...elements: Array<T>): MyArrayInstance<T> {
  return {
    push(...args) { ... },
    slice(from?: number, to?: number) { ... }
  }
}

答案 6 :(得分:1)

来自官方documentation

这是因为当类实现接口时,仅检查类的实例侧。由于构造函数位于静态端,因此它不包含在此检查中。

相反,您将需要直接使用类的静态方面。在此示例中,我们定义了两个接口,用于构造函数的ClockConstructor和用于实例方法的ClockInterface。然后,为方便起见,我们定义一个构造函数createClock,该函数创建传递给它的类型的实例:

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

function createClock(
  ctor: ClockConstructor,
  hour: number,
  minute: number
): ClockInterface {
  return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}

class AnalogClock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("tick tock");
  }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
相关问题