web-dev-qa-db-fra.com

Comment fonctionnent les interfaces avec des signatures de construction?

J'ai quelques difficultés à comprendre comment fonctionne la définition des constructeurs dans les interfaces. Je peux être totalement mal comprendre quelque chose. Mais j'ai cherché des réponses pendant un bon moment et je ne trouve rien qui y soit lié.

Comment implémenter l'interface suivante dans une classe TypeScript:

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

Anders Hejlsberg crée une interface contenant quelque chose de similaire à ceci dans cette vidéo (environ 14 minutes). Mais pour la vie de moi je ne peux pas implémenter ceci dans une classe.

Je comprends probablement mal quelque chose. Qu'est-ce que je ne reçois pas?

MODIFIER:

Clarifier. Avec "nouveau (...)" je voulais dire "n'importe quoi". Mon problème est que je ne peux même pas obtenir la version la plus basique de ce travail:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

Cela ne compile pas pour moi. "Class 'test" déclare l'interface "MyInterface" mais ne l'implémente pas: le type "MyInterface" nécessite une signature de construction, mais le type "test" en manque un "lors de la compilation.

MODIFIER:

Donc, après avoir étudié cela un peu plus compte tenu des commentaires.

interface MyInterface {
    new () : MyInterface;
}

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

N'est pas valide TypeScript et cela ne résout pas le problème. Vous ne pouvez pas définir le type de retour du constructeur. Il retournera "test". La signature de ce qui suit: class test {constructor () {}} semble être "new () => test" (obtenue en survolant "class" dans l'éditeur en ligne avec juste ce code collé dans). Et c'est ce que nous voudrions et ce que je pensais que ce serait.

Quelqu'un peut-il fournir un exemple de ceci ou quelque chose de similaire où il est en train de compiler?

EDIT (encore ...):

J'aurais donc peut-être eu une idée de la raison pour laquelle il est possible de définir cela dans une interface mais pas possible de l'implémenter dans une classe TypeScript. Les travaux suivants fonctionnent:

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

interface MyInterface {
    new () : MyInterface;
}

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

Donc, est-ce seulement une fonctionnalité de TypeScript qui vous permet d’interfacer javascript? Ou est-il possible de l'implémenter dans TypeScript sans avoir à implémenter la classe en utilisant javascript?

128
Nypan

Les signatures de construction dans les interfaces ne sont pas implémentables dans les classes; ils ne servent qu'à définir les API JS existantes qui définissent une fonction "nouvelle". Voici un exemple impliquant les interfaces new qui fonctionnent:

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

Cela crée une contrainte réelle pour ce que vous pouvez appeler makeObj avec:

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

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

En cherchant exactement la même question, j'ai cherché comment l'équipe TypeScript l'avait fait ...

Ils déclarent une interface et ensuite une variable avec un nom correspondant exactement au nom d'interface. C'est aussi le moyen de taper des fonctions statiques.

Exemple de lib.d.ts:

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

J'ai essayé ça et ça marche comme un charme.

54
Nils

Eh bien, une interface avec une signature de construction ne doit être implémentée par aucune classe (à première vue, cela peut paraître étrange pour les gars avec un arrière-plan C #/Java comme moi, mais donnez-lui une chance). C'est légèrement différent.

Pensez-y un instant à une interface avec une signature d'appel (comme un @FunctionalInterface dans Java). Elle a pour but de décrire un type de fonction .. type de. La signature décrite est supposée être satisfait par un objet fonction ... mais pas n'importe quelle fonction de haut niveau ou une méthode, mais une fonction qui sait comment construire un objet, une fonction appelée lorsque le mot-clé new est utilisé.

Donc, une interface avec une signature de construction définit la signature d'un constructeur! Le constructeur de votre classe qui doit respecter la signature définie dans l'interface (considérez-le comme si le constructeur implémentait l'interface). C'est comme un constructeur ou une usine!

Voici un court extrait de code qui tente de démontrer l'utilisation la plus courante:

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");
4
egelev

Du point de vue de la conception, il n'est pas habituel de spécifier les exigences du constructeur dans une interface. L'interface doit décrire les opérations que vous pouvez effectuer sur un objet. Différentes classes qui implémentent l'interface doivent pouvoir exiger différents paramètres de constructeur si elles en ont besoin.

Par exemple, si j'avais une interface:

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

Je pourrais avoir des implémentations pour stocker les données sous forme de cookie, qui ne nécessite aucun paramètre de constructeur, et une version qui stocke les données dans une base de données, qui nécessite une chaîne de connexion en tant que paramètre de constructeur.

Si vous voulez toujours définir des constructeurs dans une interface, il existe un moyen malavisé de le faire, auquel je répondais à cette question:

Les interfaces avec les signatures de construction ne vérifient pas le type

3
Fenton

Pour obtenir le comportement souhaité, vous pouvez utiliser Décorateurs , bien que ce ne soit probablement pas ce pour quoi ils sont censés être utilisés.

Cette

interface MyInterface {
    new ();
}

function MyInterfaceDecorator(constructor: MyInterface) {
}


@MyInterfaceDecorator
class TestClass {
    constructor () { }
}

compile sans problème. En revanche, la définition suivante de TestClass

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

ne pas compiler.

2
MarvinDV