web-dev-qa-db-fra.com

Typographie: utiliser la classe comme interface

J'ai essayé d'utiliser une classe comme interface pour une autre classe. Avec cela, j'essaie de réaliser une mockClass à jour pour les tests.

Cela devrait être possible et constitue une approche favorable, comme indiqué dans https://angular.io/guide/styleguide#interfaces

Et est démontré ici: Exporter la classe en tant qu'interface dans Angular2

Mais étrangement, j'obtiens une erreur en utilisant TypeScript 2.5.3 dans VSCode 1.17.2

Erreur dans la classe SpecialTest

[ts]
Class 'SpecialTest' incorrectly implements interface 'Test'.
  Types have separate declarations of a private property 'name'.

Samplecode:

class Test {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest implements Test {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

Qu'est-ce qui me manque?

EDIT: utilisez string au lieu de String, comme l'a suggéré @fenton

13
jvoigt

Avant de commencer, lorsque vous étendez une classe, vous utilisez le mot clé extends. Vous étendez une classe et implémentez une interface. Il y a des notes supplémentaires à ce sujet plus loin dans le post.

class SpecialTest extends Test {

Faites également attention à string vs String car cela vous fera trébucher. Vos annotations de type devraient presque certainement être string (minuscules).

Et enfin, vous n'avez pas besoin d'attribuer manuellement les paramètres du constructeur, donc l'original:

class Test {
    private name: string;

    constructor(name: string) {
        this.name = name;
    }

    // ...
}

Est mieux exprimé comme:

class Test {
    constructor(private name: string) {
    }

    // ...
}

Vous pouvez désormais choisir parmi plusieurs solutions à votre problème.

Membre protégé

Protégez le membre name, puis vous pouvez l'utiliser dans des sous-classes:

class Test {
    protected name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

class SpecialTest extends Test {
    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

Interface

C'est la solution qui, je pense, correspond le mieux à vos besoins.

Si vous extrayez les membres publics dans une interface, vous devriez pouvoir traiter les deux classes comme cette interface (que vous utilisiez explicitement ou non le mot clé implements - TypeScript est typé structurellement).

interface SomeTest {
  getName(): string;
  setName(name: string): void;
}

Vous pouvez explicitement l'implémenter si vous le souhaitez:

class SpecialTest implements SomeTest {
    private name: string;

    getName(): string {
        return '';
    }
    setName(name: string): void {
    }
}

Votre code peut désormais dépendre de l'interface plutôt que d'une classe concrète.

Utilisation d'une classe comme interface

Il est techniquement possible de référencer une classe en tant qu'interface, mais vous rencontrez des problèmes avec implements MyClass.

Premièrement, vous ajoutez une complexité inutile à quiconque doit lire votre code plus tard, y compris le futur vous. Vous utilisez également un modèle qui signifie que vous devez faire attention au mot-clé. Une utilisation accidentelle de extends peut provoquer un bug délicat à l'avenir lorsque la classe héritée est modifiée. Les responsables devront devenir hawkeye sur quel mot clé est utilisé. Et tout pour quoi? Préserver les habitudes nominales dans un langage structurel.

Les interfaces sont abstraites et moins susceptibles de changer. Les cours sont plus concrets et plus susceptibles de changer. Utiliser une classe comme interface ruine tout le concept de dépendre d'abstractions stables ... et vous oblige à la place à dépendre de classes concrètes instables.

Considérez la prolifération de "classes en tant qu'interfaces" dans un programme. Un changement dans une classe (disons que nous ajoutons une méthode) peut provoquer par inadvertance un changement d'ondulation sur de grandes distances ... combien de parties du programme rejettent maintenant l'entrée, car l'entrée ne contient pas de méthode qui n'est même pas utilisée ?

Les meilleures alternatives (lorsqu'il n'y a pas de problèmes de compatibilité avec les modificateurs d'accès) ...

Créez une interface hors de la classe:

interface MyInterface extends MyClass {
}

Ou, ne référencez pas du tout la classe d'origine dans votre deuxième classe. Laisser le système de type structurel vérifier la compatibilité.

Remarque ... selon votre configuration TSLint, les types faibles (comme une interface avec uniquement des types optionnels) déclencheront le no-empty-interface-Règle.

Cas particulier des députés

Aucune de celles-ci (utilisation d'une classe comme interface, génération d'une interface à partir d'une classe ou d'un type structurel) ne résout le problème du membre privé. C'est pourquoi la solution qui résout le vrai problème est de créer une interface avec les membres publics sur.

Dans le cas spécifique des députés, comme dans la question, réfléchissons à ce qui se passera si nous continuons avec le modèle d'origine? Le résultat naturel sera que pour conserver le modèle d'utilisation de la classe comme interface, la visibilité des membres sera modifiée, comme ceci:

class Test {
    public name: string;

    constructor(name: string) {
        this.name = name;
    }

    getName() {
        return this.name;
    }
    setName(name: string): void {
        this.name = name;
    }
}

Et maintenant, nous brisons les principes plus établis de l'orientation objet.

5
Fenton

Dans cet exemple la classe parente est censée être abstraite et agit comme une interface. Les interfaces TypeScript n'ont pas de modificateurs d'accès, toutes les propriétés d'interface devraient être publiques.

Quand une classe concrète comme Test est utilisée comme interface, un problème apparaît, car l'implémentation ne peut pas implémenter de propriétés privées et ne peut pas les remplacer non plus.

Test et SpecialTest doivent implémenter une classe abstraite comme interface (dans ce cas, la classe abstraite ne peut avoir que des membres publics) ou en hériter (dans ce cas, la classe abstraite peut avoir protected membres).

2
Estus Flask

dans le message que vous avez lié à this en avez-vous fait la dernière partie de votre implémentation:

Il convient de noter que n'importe quelle classe peut être utilisée comme interface dans> TypeScript. Donc, s'il n'y a pas vraiment besoin de faire la différence entre l'interface> et l'implémentation, il peut s'agir d'une seule classe concrète:

export class Foo {
    bar() { ... }

    baz() { ... }
 }

...
provider: [Foo]
...

Qui pourra être utilisé ultérieurement comme interface si nécessaire:

 export class SomeFoo implements Foo { ... }

 ...
 provider: [{ provide: Foo, useClass: SomeFoo }]
 ...
0
Ian Kirkpatrick

Pour suivre l'explication de @Fenton dans Utilisation d'une classe comme interface

Il existe un moyen plus explicite de déclarer class SpecialTest implements Test en utilisant InstanceType<T> type d'utilitaire. par exemple. class SpecialTest implements InstanceType<typeof Test>. Je trouve cela moins déroutant lors de la lecture du code.

Préférez les interfaces dans votre propre code, ceci - magique Je n'ai trouvé utile/nécessaire que lorsque j'avais besoin de créer un décorateur pour une classe tierce.

0
kaznovac