Typescript: Prise en charge du mot-clé de remplacement sur les méthodes de classe

Créé le 10 févr. 2015  ·  210Commentaires  ·  Source: microsoft/TypeScript

(Mise à jour par @RyanCavanuagh)

Veuillez consulter ce commentaire avant de demander "des mises à jour", "veuillez ajouter ceci maintenant", etc. Les commentaires qui n'ajoutent pas de manière significative à la discussion seront supprimés pour que la longueur du fil reste raisonnable.


(REMARQUE, ceci n'est _pas_ un doublon du numéro 1524. La proposition ici est plus dans le sens du spécificateur de remplacement C++, ce qui est beaucoup plus logique pour le tapuscrit)

Un mot-clé de remplacement serait extrêmement utile dans le tapuscrit. Ce serait un mot-clé facultatif sur toute méthode qui remplace une méthode de super classe, et similaire au spécificateur de remplacement en C++ indiquerait une intention que "_le nom + la signature de cette méthode doit toujours correspondre au nom + la signature d'une méthode de super classe_" . Cela attrape toute une gamme de problèmes dans des bases de code plus importantes qui peuvent autrement être facilement manquées.

Encore une fois similaire à C++, _ce n'est pas une erreur d'omettre le mot-clé override d'une méthode redéfinie_. Dans ce cas, le compilateur agit exactement comme il le fait actuellement et ignore les vérifications supplémentaires du temps de compilation associées au mot-clé override. Cela permet les scénarios javascript non typés plus complexes où le remplacement de la classe dérivée ne correspond pas exactement à la signature de la classe de base.

Exemple d'utilisation

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters:number):void {
    }
}

Exemple de conditions d'erreur de compilation

// Add an additional param to move, unaware that the intent was 
// to override a specific signature in the base class
class Snake extends Animal {
    override move(meters:number, height:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number,height:number):void

// Rename the function in the base class, unaware that a derived class
// existed that was overriding the same method and hence it needs renaming as well
class Animal {
    megamove(meters:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

// Require the function to now return a bool, unaware that a derived class
// existed that was still using void, and hence it needs updating
class Animal {
    move(meters:number):bool {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

IntelliSense

En plus d'une validation supplémentaire du temps de compilation, le mot-clé override fournit un mécanisme permettant à dactylographie intellisense d'afficher et de sélectionner facilement les super méthodes disponibles, où l'intention est de remplacer spécifiquement l'une d'entre elles dans une classe dérivée. Actuellement, cela est très maladroit et implique de parcourir la chaîne de super classes, de trouver la méthode que vous souhaitez remplacer, puis de la copier-coller dans la classe dérivée pour garantir la correspondance des signatures.

Mécanisme IntelliSense proposé

Dans une déclaration de classe :

  1. remplacement de type
  2. Une liste déroulante de saisie semi-automatique apparaît pour toutes les super méthodes de la classe
  3. Une méthode est sélectionnée dans la liste déroulante
  4. La signature de la méthode est émise dans la déclaration de classe après le mot-clé override.
Add a Flag Revisit Suggestion

Commentaire le plus utile

Veuillez excuser les gémissements, mais honnêtement, bien que votre argument s'applique au mot-clé public dans un langage où tout est public par défaut, ce qui divise le monde en effet, ayant des choses comme abstract et un override facultatif Le mot-clé

La redéfinition est l'un des rares aspects restants du langage hautement sensibles à la typo, car un nom de méthode de redéfinition mal typé n'est pas un problème de temps de compilation évident. L'avantage de override est évident, car il vous permet d'indiquer votre intention de remplacer - si la méthode de base n'existe pas, c'est une erreur de compilation. Tous saluent le système de type. Pourquoi quelqu'un ne voudrait-il pas cela?

Tous les 210 commentaires

En effet une bonne proposition.

Cependant, qu'en est-il des exemples suivants, qui sont des remplacements valides pour moi :

class Snake extends Animal {
    override move(meters:number, height=-1):void {
    }
}
class A {...}
class Animal {
    setA(a: A): void {...}
    getA(): A {...}
}

class B extends A {...}
class Snake extends Animal {
    override setA(a: B): void {...}
    override getA(): B {...}
}

De plus, j'ajouterais un indicateur de compilateur pour forcer le mot-clé override à être présent (ou signalé comme un avertissement).
La raison est d'attraper lors du renommage d'une méthode dans une classe de base que les classes héritées implémentent déjà (mais pas censé être un remplacement).

Ah de beaux exemples. D'une manière générale, je m'attendrais à ce que l'utilisation du mot-clé override applique une correspondance _exacte_ des signatures, car le but de son utilisation est de maintenir une hiérarchie de classes typée stricte. Donc pour répondre à vos exemples:

  1. Ajout d'un paramètre par défaut supplémentaire. Cela générerait une erreur de compilation : Snake super ne définit pas move(meters:number):void. Bien que la méthode dérivée soit fonctionnellement cohérente, le code client appelant Animal.move ne peut pas s'attendre à ce que les classes dérivées prennent également en compte la hauteur (car l'API de base ne l'expose pas).
  2. Cela générerait (et devrait toujours) générer une erreur de compilation, car il n'est pas cohérent sur le plan fonctionnel. Considérez l'ajout suivant à l'exemple :
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Ainsi, l'exemple (2.) est en fait une excellente démonstration de la façon dont un mot-clé override peut détecter des cas subtils au moment de la compilation qui seraient autrement manqués ! :)

Et je soulignerais à nouveau que les deux exemples peuvent être valides dans des scénarios javascript contrôlés/avancés spécifiques qui peuvent être requis... dans ce cas, les utilisateurs peuvent simplement choisir d'omettre le mot-clé override.

Ce sera utile. Nous travaillons actuellement autour de cela en incluant une référence factice à la super méthode :

class Snake extends Animal {
    move(meters:number, height?:number):void {
         super.move; // override fix
    }
}

Mais cela ne fait que protéger contre le deuxième cas : les super méthodes sont renommées. Les changements de signature ne déclenchent pas d'erreur de compilation. De plus, il s'agit clairement d'un hack.

Je ne pense pas non plus que les paramètres par défaut et facultatifs dans la signature de la méthode de classe dérivée devraient déclencher une erreur de compilation. Cela peut être correct mais va à l'encontre de la flexibilité inhérente de JavaScript.

@rwyborn
Il semble que nous ne nous attendions pas au même comportement.
Vous utiliseriez ce mot-clé override pour assurer une même signature, alors que je l'utiliserais plutôt comme une option de lisibilité (d'où ma demande d'ajouter une option de compilateur pour forcer son utilisation).
En fait, ce à quoi je m'attendrais vraiment, c'est que TS détecte les méthodes de remplacement invalides (même sans l'utilisation de la substitution).
Typiquement:

class Snake extends Animal {
    move(meters:number, height:number):void {}
}

devrait générer une erreur, car il s'agit en réalité d'un remplacement de Animal.move() (comportement JS), mais incompatible (car la hauteur n'est pas censée être facultative, alors qu'elle sera indéfinie si elle est appelée depuis une "référence" Animal).
En fait, utiliser override ne ferait que confirmer (par le compilateur) que cette méthode existe réellement dans la classe de base (et donc avec une signature conforme, mais en raison du point précédent, pas en raison du mot-clé override).

@stephanedr , parlant en tant qu'utilisateur unique, je suis d'accord avec vous sur le fait que le compilateur doit toujours confirmer la signature, car j'aime personnellement imposer un typage strict dans mes hiérarchies de classes (même si javascript ne le fait pas !!).

Cependant, en proposant que ce comportement soit facultatif via le mot-clé override, j'essaie de garder à l'esprit que finalement javascript n'est pas typé, et donc l'application de la correspondance de signature stricte par défaut entraînerait que certains modèles de conception javascript ne seraient plus exprimables dans Typescript.

@rwyborn Je suis content que vous ayez mentionné l'implémentation C++ car c'est exactement comme ça que j'imaginais que cela devrait fonctionner avant d'arriver ici - éventuellement. Cependant, un indicateur de compilateur qui forcerait l'utilisation du mot-clé override irait bien dans mon livre.

Le mot-clé permettrait des erreurs de temps de compilation pour une frappe maladroite des développeurs, ce qui m'inquiète le plus à propos des remplacements dans leur forme actuelle.

class Base {
    protected commitState() : void {

    }
}


class Implementation extends Base {
    override protected comitState() : void {   /// error - 'comitState' doesn't exist on base type

    }
}

Actuellement (à partir de 1.4), la classe Implementation ci-dessus déclarerait simplement une nouvelle méthode et le développeur n'en serait pas plus avisé jusqu'à ce qu'il remarque que son code ne fonctionne pas.

Discuté lors de l'examen des suggestions.

Nous comprenons parfaitement les cas d'utilisation ici. Le problème est que l'ajouter à ce stade du langage ajoute plus de confusion qu'il n'en supprime. Une classe avec 5 méthodes, dont 3 sont marquées override , n'impliquerait pas que les 2 autres _ne soient pas_ des substitutions. Pour justifier son existence, le modificateur aurait vraiment besoin de diviser le monde plus proprement que cela.

Veuillez excuser les gémissements, mais honnêtement, bien que votre argument s'applique au mot-clé public dans un langage où tout est public par défaut, ce qui divise le monde en effet, ayant des choses comme abstract et un override facultatif Le mot-clé

La redéfinition est l'un des rares aspects restants du langage hautement sensibles à la typo, car un nom de méthode de redéfinition mal typé n'est pas un problème de temps de compilation évident. L'avantage de override est évident, car il vous permet d'indiquer votre intention de remplacer - si la méthode de base n'existe pas, c'est une erreur de compilation. Tous saluent le système de type. Pourquoi quelqu'un ne voudrait-il pas cela?

Je suis d'accord à 100% avec @hdachev , la petite incohérence mentionnée également par @RyanCavanaugh est facilement compensée par les avantages du mot-clé pour apporter des vérifications de temps de compilation aux remplacements de méthode. Je voudrais à nouveau souligner que C++ utilise avec succès un mot-clé optionnel override exactement de la même manière que suggéré pour le tapuscrit.

Je ne saurais trop insister sur la différence que fait la vérification de priorité dans une base de code à grande échelle avec des arborescences OO complexes.

Enfin, j'ajouterais que si l'incohérence d'un mot-clé facultatif est vraiment un problème, alors l'approche C# pourrait être utilisée, c'est-à-dire l'utilisation obligatoire des mots-clés "new" ou "override":

class Dervied extends Base {

    new FuncA(newParam) {} // "new" says that I am implementing a new version of FuncA() with a different signature to the base class version

    override FuncB() {} // "override" says that I am implementing exactly the same signature as the base class version

    FuncC() {} // If FuncC exists in the base class then this is a compile error. I must either use the override keyword (I am matching the signature) or the new keyword (I am changing the signature)
}

Ce n'est pas analogue à public car une propriété sans modificateur d'accès est connue pour être publique ; une méthode sans override n'est _pas_ connue pour être non prioritaire.

Voici une solution vérifiée à l'exécution utilisant des décorateurs (venant dans TS1.5) qui produit de bons messages d'erreur avec très peu de temps système :

/* Put this in a helper library somewhere */
function override(container, key, other1) {
    var baseType = Object.getPrototypeOf(container);
    if(typeof baseType[key] !== 'function') {
        throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method');
    }
}

/* User code */
class Base {
    public baseMethod() {
        console.log('Base says hello');
    }
}

class Derived extends Base {
    // Works
    <strong i="9">@override</strong>
    public baseMethod() {
        console.log('Derived says hello');
    }

    // Causes exception
    <strong i="10">@override</strong>
    public notAnOverride() {
        console.log('hello world');
    }
}

L'exécution de ce code génère une erreur :

Erreur : la méthode notAnOverride de Derived ne remplace aucune méthode de classe de base

Étant donné que ce code s'exécute au moment de l'initialisation de la classe, vous n'avez même pas besoin de tests unitaires spécifiques aux méthodes en question ; l'erreur se produira dès que votre code sera chargé. Vous pouvez également utiliser une version "rapide" de override qui ne vérifie pas les déploiements de production.

@RyanCavanaugh Nous sommes donc à Typescript 1.6 et les décorateurs sont toujours une fonctionnalité expérimentale, pas quelque chose que je veux déployer dans une base de code de production à grande échelle comme un hack pour faire fonctionner le remplacement.

Pour essayer encore un autre angle, chaque langage typé populaire prend en charge le mot-clé "override" ; Swift, ActionScript, C#, C++ et F# pour n'en nommer que quelques-uns. Toutes ces langues partagent les problèmes mineurs que vous avez exprimés dans ce fil de discussion sur le remplacement, mais il est clair qu'il existe un grand groupe qui voit que les avantages du remplacement pèsent de loin sur ces problèmes mineurs.

Vos objections sont-elles purement basées sur le rapport coût/bénéfice ? Si je devais réellement aller de l'avant et mettre cela en œuvre dans un PR, cela serait-il accepté ?

Ce n'est pas seulement une question de coût/bénéfice. Comme Ryan l'a expliqué, le problème est que le fait de marquer une méthode comme un remplacement n'implique pas qu'une autre méthode _n'est pas_ un remplacement. La seule façon dont cela aurait du sens est si tous les remplacements devaient être marqués avec un mot-clé override (ce qui, si nous l'imposions, serait un changement de rupture).

@DanielRosenwasser Comme indiqué ci-dessus, en C++, le mot-clé override est facultatif (exactement comme proposé pour Typescript) mais tout le monde l'utilise sans problème et il est extrêmement utile sur de grandes bases de code. De plus, dans Typescript, il est en fait très logique qu'il soit facultatif en raison de la surcharge des fonctions javascript.

class Base {
    method(param: number): void { }
}

class DerivedA extends Base {
    // I want to *exactly* implement method with the same signature
    override method(param: number): void {}
}

class DerivedB extends Base {
    // I want to implement method, but support an extended signature
    method(param: number, extraParam: any): void {}
}

Quant à l'ensemble de l'argument "n'implique pas qu'une autre méthode n'est pas un remplacement", il est exactement analogue à "privé". Vous pouvez écrire une base de code entière sans jamais utiliser le mot-clé private. Certaines des variables de cette base de code ne seront accessibles qu'en privé et tout se compilera et fonctionnera correctement. Cependant, "privé" est un sucre syntaxique supplémentaire que vous pouvez utiliser pour dire au compilateur "Non vraiment, erreur de compilation si quelqu'un essaie d'y accéder". De la même manière, la "surcharge" est un sucre syntaxique supplémentaire pour dire au compilateur "Je veux que cela corresponde exactement à la déclaration de classe de base. Si cela ne compile pas l'erreur".

Vous savez quoi, je pense que vous êtes obsédés par l'interprétation littérale de "override". Vraiment ce qu'il marque est "exactly_match_signature_of_superclass_method", mais ce n'est pas aussi lisible :)

class DerivedA extends Base {
    exactly_match_signature_of_superclass_method method(param: number): void {}
}

J'aimerais moi aussi que le mot-clé override soit disponible et que le compilateur génère une erreur si la méthode marquée pour le remplacement n'existe pas ou a une signature différente dans la classe de base. Cela aiderait à la lisibilité et à la refactorisation

+1, l'outillage s'améliorera également beaucoup. Mon cas d'utilisation utilise react. Je dois vérifier la définition à chaque fois que j'utilise les méthodes ComponentLifecycle :

``` C#
interface ComponentLifecycle

{
componentWillMount?() : void ;
componentDidMount?() : void ;
componentWillReceiveProps ?(nextProps : P, nextContext : any) : void ;
shouldComponentUpdate ?(nextProps : P, nextState : S, nextContext : any) : booléen ;
componentWillUpdate?(nextProps : P, nextState : S, nextContext : any) : void ;
componentDidUpdate ?(prevProps : P, prevState : S, prevContext : any): void ;
componentWillUnmount?() : void ;
}

With override, or other equivalent solution,you'll get a nice auto-completion. 

One problem however is that I will need to override interface methods...

``` C#
export default class MyControlextends React.Component<{},[}> {
    override /*I want intellisense here*/ componentWillUpdate(nextProps, nextState, nextContext): void {

    }
}

@olmobrutall , il semble que votre cas d'utilisation soit mieux résolu par le service linguistique offrant des refactorisations telles que "implémenter l'interface" ou offrant une meilleure complétion, et non en ajoutant un nouveau mot-clé au langage.

Ne nous laissons pas distraire :) Les fonctionnalités du service de langage ne sont qu'une petite partie du maintien des hiérarchies d'interface dans une grande base de code. De loin, la plus grande victoire est en fait d'obtenir des erreurs de temps de compilation lorsqu'une classe quelque part dans votre hiérarchie n'est pas conforme. C'est pourquoi C++ a ajouté un mot-clé optionnel override (changement sans rupture). C'est pourquoi Typescript devrait faire de même.

La documentation de Microsoft pour le remplacement de C++ résume bien les choses, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Utilisez override pour éviter tout comportement d'héritage par inadvertance dans votre code. L'exemple suivant montre où, sans utiliser override, le comportement de la fonction membre de la classe dérivée peut ne pas avoir été prévu. Le compilateur n'émet aucune erreur pour ce code.
...
Lorsque vous utilisez override, le compilateur génère des erreurs au lieu de créer silencieusement de nouvelles fonctions membres.

De loin, la plus grande victoire est en fait d'obtenir des erreurs de temps de compilation lorsqu'une classe quelque part dans votre hiérarchie n'est pas conforme.

Je suis d'accord. Un écueil qui surgit dans nos équipes est que les gens pensent qu'ils ont surchargé des méthodes alors qu'en fait ils ont légèrement mal tapé ou étendu la mauvaise classe.

Les changements d'interface dans une bibliothèque de base lorsque vous travaillez sur de nombreux projets sont plus difficiles qu'ils ne devraient l'être dans un langage avec un agenda comme TypeScript.

Nous avons tellement de choses géniales, mais il y a aussi des bizarreries comme celle-ci et aucune propriété const au niveau de la classe.

@RyanCavanaugh vrai, mais le service linguistique pourrait être déclenché après l'écrasement de l'écriture, comme le font de nombreuses langues, sans le mot-clé, il est beaucoup plus difficile de déterminer quand est le bon moment.

À propos de l'interface d'implémentation, notez que la plupart des méthodes de l'interface sont facultatives et que vous êtes censé remplacer uniquement celles dont vous avez besoin, et non l'ensemble du package. Vous pouvez ouvrir une boîte de dialogue avec des cases à cocher, mais toujours ...

Et bien que mon problème actuel soit de trouver le nom de la méthode, à l'avenir, ce sera formidable d'être averti des erreurs de compilation si quelqu'un renomme ou modifie la signature d'une méthode de base.

L'incohérence ne pourrait-elle pas être résolue simplement en ajoutant un avertissement lorsque vous n'écrivez pas de remplacement? Je pense que le tapuscrit fait ce qu'il faut en ajoutant de petites modifications raisonnables au lieu de conserver les mauvaises décisions pour la fin des temps.

Aussi l'abstrait est déjà là, ils formeront un couple génial :)

J'ai également ressenti le besoin d'un spécificateur "override". Dans les projets de taille moyenne à grande, cette fonctionnalité devient essentielle et avec tout le respect que je vous dois, j'espère que l'équipe Typescript reconsidérera la décision de refuser cette suggestion.

Pour toute personne intéressée, nous avons écrit une règle tslint personnalisée qui fournit la même fonctionnalité que le mot-clé override en utilisant un décorateur, similaire à la suggestion de Ryan ci-dessus mais vérifiée au moment de la compilation. Nous l'ouvrirons bientôt, je posterai quand il sera disponible.

J'ai également fortement ressenti la nécessité du mot-clé "override".
Dans mon cas, j'ai changé certains noms de méthodes dans une classe de base et j'ai oublié de renommer certains noms de méthodes prioritaires. Bien sûr, cela entraîne certains bogues.

Mais, s'il y avait une telle fonctionnalité, nous pouvons facilement trouver ces méthodes de classe.

Comme @RyanCavanaugh l'a mentionné, si ce mot-clé n'est qu'un mot-clé facultatif, cette fonctionnalité prête à confusion. Alors, que diriez-vous de créer un drapeau dans tsc pour activer cette fonctionnalité ?

Veuillez reconsidérer cette fonctionnalité à nouveau....

Pour moi, si le mot-clé override doit être utile, il doit être appliqué, comme c'est le cas en C#. Si vous spécifiez une méthode en C# qui remplace la signature d'une méthode de base, vous _devez_ l'étiqueter override ou new.

C++ est ennuyeux et inférieur par rapport à C# à certains endroits car il rend trop de mots-clés facultatifs et empêche donc la _cohérence_. Par exemple, si vous surchargez une méthode virtuelle, la méthode surchargée peut être marquée comme virtuelle ou non - dans tous les cas, elle sera virtuelle. Je préfère ce dernier car il aide les autres développeurs qui lisent le code, mais je n'arrive pas à faire en sorte que le compilateur l'applique, ce qui signifie que notre base de code aura sans aucun doute des mots-clés virtuels manquants là où ils devraient vraiment être. Le mot-clé override est également facultatif. Les deux sont de la merde à mon avis. Ce qui est négligé ici, c'est que le code peut servir de documentation et améliorer la maintenabilité en imposant le besoin de mots-clés plutôt qu'une approche "à prendre ou à laisser". Le mot clé "throw" en C++ est similaire.

Pour atteindre l'objectif ci-dessus dans TypeScript, le compilateur a besoin d'un indicateur pour "activer" ou non ce comportement strict.

En ce qui concerne les signatures de fonction de la base et de la dérogation, elles doivent être identiques. Mais il serait souhaitable de permettre la covariance dans la spécification du type de retour pour appliquer une vérification au moment de la compilation.

J'arrive sur TS depuis AS3, donc bien sûr, je vais également voter ici pour un mot-clé override . Pour un développeur qui découvre une base de code donnée, voir un override est un énorme indice sur ce qui pourrait se passer dans une classe (enfant). Je pense qu'un tel mot-clé ajoute énormément de valeur. Je choisirais de le rendre obligatoire, mais je peux voir comment ce serait un changement de rupture et devrait donc être facultatif. Je ne vois vraiment pas de problème avec l'ambiguïté que cela impose, même si je suis sensible à la différence entre un mot-clé optionnel override et le mot-clé par défaut public .

Pour tous les +1ers -- pouvez-vous parler de ce qui n'est pas suffisant dans la solution de décorateur présentée ci-dessus ?

Pour moi, cela ressemble à une construction artificielle, pas à une propriété de la langue elle-même. Et je suppose que c'est par conception, parce que c'est ce que c'est. Ce qui, je suppose, envoie au développeur (enfin, moi en tout cas) le message que c'est transitoire, pas une meilleure pratique.

évidemment, chaque langage a son propre paradigme et, étant nouveau sur TypeScript, je suis lent avec le changement de paradigme. Je dois dire cependant que override me semble être une meilleure pratique pour un certain nombre de raisons. Je passe à TypeScript parce que j'ai complètement avalé le koolaid fortement typé et je pense que le coût (en termes de frappes et de courbe d'apprentissage) est largement compensé par les avantages à la fois pour le code sans erreur et la compréhension du code. override est une pièce très importante de ce puzzle et communique des informations très importantes sur le rôle de la méthode prioritaire.

Pour moi, il s'agit moins de commodité de l'IDE, même si c'est indéniablement génial lorsqu'il est correctement pris en charge, et plus de respecter ce que je pense être les principes sur lesquels vous avez construit ce langage.

@RyanCavanaugh quelques problèmes que je vois :

  • La syntaxe du décorateur sera plus difficile pour les IDE pour fournir l'achèvement du code (oui, je suppose que cela pourrait être fait mais ils auraient besoin de connaissances préalables)
  • La classe dérivée pourrait modifier la visibilité d'une méthode - ce qui, à proprement parler, ne devrait pas être autorisé
  • Difficile pour le décorateur de vérifier la liste et les types d'arguments, et le type de retour compatible

Cependant, le compilateur vérifie déjà la liste des arguments et le type de retour. Et je pense que même si override existait en tant que mot-clé de première classe, nous n'appliquerions pas de nouvelle règle sur l'identité des signatures

Et je pense que même si override existait en tant que mot-clé de première classe, nous n'imposerions pas une nouvelle règle sur l'identité des signatures.

@RyanCavanaugh alors je pense que vous pourriez être sur une page différente concernant l'intention du mot-clé. L'intérêt est pour les cas où vous _voulez_ imposer l'identité de la signature. C'est pour le modèle de conception où vous avez une hiérarchie complexe profonde assise sur une classe de base qui définit un contrat de méthodes d'interface, et toutes les classes de la hiérarchie doivent correspondre _exactement_ à ces signatures. En ajoutant le mot-clé override sur ces méthodes dans toutes les classes dérivées, vous êtes alerté de tous les cas où la signature s'écarte du contrat défini dans la classe de base.

Comme je n'arrête pas de le dire, ce n'est pas un cas d'utilisation ésotérique. Lorsque vous travaillez sur de grandes bases de code (avec, par exemple, des centaines ou des milliers de classes), cela se produit _tous les jours_, c'est-à-dire que quelqu'un doit changer la signature de la classe de base (modifier le contrat), et vous voulez que le compilateur vous avertisse de tout cas dans toute la hiérarchie qui ne correspondent plus.

Le compilateur avertira déjà des remplacements illégaux. Le problème avec le
la mise en œuvre actuelle est le manque d'intention lors de la déclaration d'un remplacement
méthode.

Le décorateur mentionné précédemment semble juste aller à l'encontre de ce que la langue
essaie d'atteindre. Il ne devrait pas y avoir de coût d'exécution encouru pour
quelque chose qui peut être traité au moment de la compilation, quel que soit le coût.
Nous voulons détecter autant de choses qui ne vont pas que possible, sans avoir besoin de
exécutez le code pour le savoir.

Il est possible de réaliser en utilisant le décorateur et de personnaliser un tslint personnalisé
règle, mais serait mieux pour l'écosystème d'outillage et la communauté pour
la langue pour avoir un mot-clé officiel.

Je pense qu'être facultatif est une approche, mais comme avec certains compilateurs C++
il y a des drapeaux que vous pouvez définir qui imposent son utilisation (par exemple, suggérer-écraser,
remplacement incohérent-manquant). Cela semble être la meilleure approche pour éviter
modifications avec rupture et semble cohérent avec les autres nouvelles fonctionnalités ajoutées
récemment, tels que les types nullables et les décorateurs.

Le mercredi 23 mars 2016 à 21h31, Rowan Wyborn [email protected] a écrit :

Et je pense que même si override existait en tant que mot-clé de première classe, nous
n'appliquerait pas une nouvelle règle sur l'identité des signatures.

@RyanCavanaugh https://github.com/RyanCavanaugh alors je pense que vous pourriez
être sur une page différente concernant l'intention du mot-clé. Tout l'intérêt de
c'est pour les cas où vous _voulez_ imposer l'identité de la signature. Ce
est pour le modèle de conception où vous avez une hiérarchie complexe profonde assis
sur une classe de base qui définit un contrat de méthodes d'interface, et tout
les classes de la hiérarchie doivent _exactement_ correspondre à ces signatures. En ajoutant
le mot clé override sur ces méthodes dans toutes les classes dérivées, vous êtes
alerté de tous les cas où la signature s'écarte du contrat énoncé
dans la classe de base.

Comme je n'arrête pas de le dire, ce n'est pas un cas d'utilisation ésotérique. Lorsque vous travaillez sur un
grandes bases de code (avec disons des centaines ou des milliers de classes) c'est un _chaque
day_occurrence, c'est-à-dire que quelqu'un doit changer la signature de la base
class (modifier le contrat), et vous voulez que le compilateur vous avertisse de tout
cas dans toute la hiérarchie qui ne correspondent plus.


Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -200551774

@kungfusheep fwiw le compilateur n'attrape qu'une certaine classe de remplacements illégaux, c'est là qu'il y a un conflit direct entre les paramètres déclarés. Il n'intercepte _pas_ l'ajout ou la suppression de paramètres, pas plus qu'il n'intercepte les changements de type de retour. Ces vérifications supplémentaires sont ce que tout mot-clé de remplacement activerait.

Le compilateur vous avertit correctement si vous _ajoutez_ un paramètre à une fonction prioritaire.

La suppression d'un paramètre, cependant, est _totalement valide_ :

class BaseEventHandler {
  handleEvent(e: EventArgs, timestamp: number) { }
}

class DerivedEventHandler extends BaseEventHandler {
  handleEvent(e: EventArgs) {
    // I don't need timestamp, it's OK
  }
}

Changer le type de retour est également _totalement valide_ :

class Base {
  specialClone(): Base { ... }
}
class Derived extends Base {
  specialClone(): Derived { ... }
}

@RyanCavanaugh oui, ils sont valables d'un point de vue linguistique strict, mais c'est pourquoi j'ai commencé à utiliser le terme "contrat" ​​ci-dessus. Si une classe de base présente une signature spécifique, lorsque j'utilise override, je déclare que je veux que ma classe dérivée suive strictement cette signature. Si la classe de base ajoute des paramètres supplémentaires ou modifie le type de retour, je souhaite connaître chaque point de ma base de code qui ne correspond plus, car le contrat sur lequel ils ont été écrits à l'origine a maintenant changé d'une manière ou d'une autre.

L'idée d'avoir une grande hiérarchie de classes, chacune avec ses propres permutations légèrement différentes des méthodes de base (même si elles sont valables du point de vue du langage) est cauchemardesque et me ramène au mauvais vieux temps de javascript avant l'arrivée du tapuscrit :)

Si l'optionnalité est le principal problème avec le mot-clé, alors pourquoi ne pas le rendre obligatoire lorsque la méthode de classe de base est définie comme abstract ? De cette façon, si vous vouliez appliquer strictement le modèle, vous pouvez simplement ajouter une classe de base abstraite. Pour éviter de casser le code, un commutateur de compilateur peut désactiver la vérification.

Il semble que nous parlions maintenant de deux séries d'attentes différentes. Il y a la situation de remplacement manquant / de remplacement illégal, puis il y a celle de la signature explicite. Pouvons-nous tous convenir que le premier est le strict minimum absolu que nous attendons de ce mot clé ?

Je dis cela uniquement parce qu'il existe actuellement d'autres moyens d'appliquer des signatures de méthode explicites, telles que les interfaces, mais il n'existe actuellement aucun moyen d'indiquer explicitement l'intention de remplacer.

Ok, il n'y a aucun moyen d'appliquer des signatures explicites de méthodes remplacées, mais étant donné que le compilateur applique que toutes les modifications de signature sont au moins "sûres", il semble qu'il y ait une conversation distincte à avoir autour de la solution à ce problème.

Ouais d'accord. Si je devais choisir, la situation de dépassement manquant / de remplacement illégal est le problème le plus important à résoudre.

Je suis un peu en retard pour la fête... Je pense que l'intérêt de Typescript est d'appliquer les règles au moment de la compilation, pas au moment de l'exécution, sinon nous utiliserions tous du Javascript simple. De plus, c'est un peu bizarre d'utiliser des hacks/kludges pour faire des choses qui sont standard dans tant de langues.
Devrait-il y avoir un mot-clé override dans Typescript ? Je le crois certainement. Doit-il être obligatoire? Pour des raisons de compatibilité, je dirais que son comportement pourrait être spécifié avec un argument de compilateur. Doit-il appliquer la signature exacte ? Je pense que cela devrait être une discussion séparée, mais je n'ai eu aucun problème avec le comportement actuel jusqu'à présent.

La raison initiale de la fermeture semble être que nous ne pouvons pas introduire le changement de rupture selon lequel toutes les méthodes remplacées ont besoin du spécificateur override , ce qui le rendrait déroutant puisque les méthodes non marquées avec override pourraient également dans fait être outrepasse.

Pourquoi ne pas l'appliquer, soit avec une option du compilateur, et/ou s'il y a _au moins une_ méthode marquée override dans la classe, alors toutes les méthodes qui sont des remplacements doivent être marquées comme telles, sinon c'est une erreur ?

Cela vaut-il la peine de le rouvrir pendant qu'il est en l'air ?

Le vendredi 8 avril 2016 à 14h38, Peter Palotas [email protected] a écrit :

La raison initiale de la fermeture semble être que nous ne pouvons pas introduire
le changement de rupture que toutes les méthodes remplacées ont besoin de la substitution
spécificateur, ce qui rendrait la confusion puisque les méthodes non marquées par
'override' pourrait aussi être en fait des overrides.

Pourquoi ne pas l'imposer, soit avec une option du compilateur, et/ou s'il y a au
moins 'une' méthode marquée override dans la classe, puis toutes les méthodes qui sont
les remplacements doivent être marqués comme tels, sinon c'est une erreur ?


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207434898

Je dis s'il vous plaît rouvrez! Je serais heureux avec une option de compilateur.

oui - veuillez rouvrir

Rouvrez ça !

ES7 ne nécessite-t-il pas cette substitution/surcharge de plusieurs méthodes ayant la même
Nom?
Le 8 avril 2016 à 10h56, "Aram Taieb" [email protected] a écrit :

Oui, veuillez rouvrir ceci !


Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207466464

+1 - pour moi, c'est la plus grande lacune dans la sécurité des types que j'ai dans le développement quotidien de TypeScript.

Chaque fois que j'implémente une méthode de cycle de vie React comme "componentDidMount", je recherche la page de documentation React pertinente et copie/colle le nom de la méthode, pour m'assurer que je n'ai pas de faute de frappe. Je fais cela parce que cela prend 20 secondes alors que les bogues d'une faute de frappe sont indirects et subtils, et peuvent prendre beaucoup plus de temps à détecter.

class avec 5 méthodes, dont 3 sont marquées comme override, n'impliquerait pas que les 2 autres ne soient pas overrides. Pour justifier son existence, le modificateur aurait vraiment besoin de diviser le monde plus proprement que cela.

S'il s'agit de la principale préoccupation, appelez le mot-clé check_override pour indiquer clairement que vous choisissez de remplacer la vérification, et par implication, les autres méthodes sont _non vérifiées_ plutôt que _non remplacées_.

Qu'en est-il de l'utilisation implements. ? Être quelque chose comme ça :

class MyComponent extends React.Component<MyComponentProps, void>{
    implements.componentWillMount(){
        //do my stuff
    }
}

Cette syntaxe présente certains avantages :

  • Il utilise un mot-clé déjà existant.
  • Après avoir écrit implements. , l'IDE a une excellente opportunité d'afficher une fenêtre contextuelle d'auto-complétion.
  • le mot-clé précise qui peut être utilisé pour forcer la vérification sur les méthodes abstraites des classes de base mais aussi sur les interfaces implémentées.
  • la syntaxe est suffisamment ambiguë pour être utilisable aussi pour les champs, comme ceci :
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

    implements.componentWillMount(){
        //do my stuff
    }
}

Remarque : Alternativement, nous pourrions utiliser base. , c'est plus trieur et plus intuitif, mais peut-être plus déroutant (définir ou appeler ?) et la signification n'est pas si compatible avec l'implémentation de l'interface. Exemple:

class MyComponent<MyComponentProps, MyComponentState> {
    base.state = {/*auto-completion for MyComponentState here*/};

    base.componentWillMount(){ //DEFINING
        //do my stuff
        base.componentWillMount(); //CALLING
        //do other stuff
    }
}

Je ne pense pas que implements couvre suffisamment tous les cas d'utilisation
mentionné précédemment et c'est un peu ambigu. ça ne donne plus rien
informations à un IDE que override ne pouvait pas non plus, il semblerait donc
logique de s'en tenir à la terminologie que de nombreuses autres langues ont utilisée pour
réaliser la même chose.
Le mercredi 13 avril 2016 à 19h06, Olmo [email protected] a écrit :

Qu'en est-il de l'utilisation d'outils ? Être quelque chose comme ça :

la classe MyComponent étend React.Component{
met en œuvre.componentWillMount(){
//faire mes affaires
}
}

Cette syntaxe présente certains avantages :

  • Il utilise un mot-clé déjà existant.
  • Après avoir écrit met en œuvre. l'IDE a une excellente opportunité de montrer
    une méthode d'autocomplétion.
  • le mot clé clarifie qui peut être utilisé pour forcer la vérification sur le résumé
    méthodes des classes de base mais aussi des interfaces implémentées.
  • la syntaxe est suffisamment ambiguë pour être utilisable aussi pour les champs, comme
    ce:

classe MonComposant{
implements.state = {/_auto-completion for MyComponentState here_/} ;

implements.componentWillMount(){
    //do my stuff
}

}


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209571753

Le problème avec le remplacement est qu'une fois que vous utilisez le même mot clé, vous avez les mêmes attentes :

  • il devrait être obligatoire, du moins si la méthode est abstract .
  • vous manquez alors un mot-clé virtual pour annoter les méthodes qui sont censées être remplacées mais qui ont un comportement par défaut.

Je pense que l'équipe TS ne veut pas ajouter autant de bagages OO au langage, et je pense que c'est une bonne idée.

En utilisant implements. , vous disposez d'une syntaxe légère pour obtenir les principaux avantages : la saisie semi-automatique et le nom vérifié au moment de la compilation, sans inventer de nouveaux mots clés ni incrémenter le nombre de concepts.

A également l'avantage de fonctionner pour les classes et les interfaces, et pour les méthodes (dans le prototype) ou les champs directs.

Les moyens de rendre override obligatoire ont déjà été discutés dans le fil et les solutions ne sont pas différentes des autres fonctionnalités implémentées par le compilateur.

Le mot-clé virtual n'a pas vraiment de sens dans le contexte du langage et il n'est pas non plus nommé d'une manière intuitive pour les personnes qui n'ont jamais utilisé de langages comme C++ auparavant. Peut-être qu'une meilleure solution pour fournir ce type de garde, si nécessaire, serait le mot-clé final .

Je conviens que les fonctionnalités linguistiques ne doivent pas être ajoutées à la hâte, ce qui pourrait créer des «bagages», mais override comble un trou légitime dans le système d'héritage que de nombreuses autres langues ont jugé nécessaire. Sa fonctionnalité est mieux réalisée avec un nouveau mot-clé, certainement lorsque des approches alternatives suggèrent des changements de syntaxe fondamentaux.

Qu'en est-il de la définition de champs hérités plutôt que de la redéfinition de nouveaux ? React state en est un bon exemple.

Ou mettre en œuvre des méthodes d'interface facultatives ?

le remplacement pourrait potentiellement être utilisé sur les champs, s'ils sont redéclarés
dans le corps de la sous-classe. Les méthodes d'interface facultatives ne sont pas remplacées, elles le sont donc
en dehors du cadre de ce dont nous parlons ici.

Le jeudi 14 avril 2016 à 11h58, Olmo [email protected] a écrit :

Qu'en est-il de la définition de champs hérités plutôt que de la redéfinition de nouveaux ? État de réaction
est un bon exemple.

Ou mettre en œuvre des méthodes d'interface facultatives ?


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209879217

Les méthodes d'interface facultatives ne sont pas des remplacements et sortent donc du cadre de ce dont nous parlons ici.

Les méthodes d'interface facultatives sont un concept assez unique à TypeScript pour autant que je sache, et je pense qu'une implémentation TypeScript de "substitutions" devrait s'appliquer à elles.

Dans le cas des méthodes de cycle de vie React telles que componentDidMount, il s'agit de méthodes d'interface facultatives qui n'ont pas d'implémentation dans la superclasse.

Dans le cas des méthodes de cycle de vie React telles que componentDidMount, il s'agit de méthodes d'interface facultatives qui n'ont pas d'implémentation dans la superclasse.

Exactement, pour qu'ils ne remplacent rien. Je pense que nous ne comprenons pas ce que le mot-clé override est destiné à fournir ici, car si vous cherchez simplement un moyen d'obtenir une meilleure indication de code/intellisense, il existe d'autres moyens qui peuvent être obtenus sans ajouter de nouveaux mots-clés à la langue.

Les gars, avec tout le respect que je vous dois, pouvons-nous rester concentrés. Je pense que discuter de mots-clés alternatifs est contre-productif, d'autant plus que le problème a été lancé avec une demande spécifique.

Pouvons-nous tous convenir que nous avons besoin override et demander gentiment à l'équipe Typescript de l'ajouter ou abandonner la demande et laisser le problème fermé ?

Je pense qu'il est toujours utile de poser la demande dans le contexte du problème, il y a toujours différentes façons de résoudre le problème. Le point de friction avec le remplacement était qu'il soit obligatoire ou non (certains faisant valoir qu'il n'est pas obligatoire en C++). Le problème auquel beaucoup de gens ont fait référence est d'obtenir le bon nom pour les fonctions remplaçables (qui peuvent ou non être implémentées dans la super classe) - les fonctions de cycle de vie de React étant le principal problème.

Si le remplacement ne fonctionne pas, nous devrions peut-être fermer ce problème et ouvrir un point plus général sur ce problème. Le problème général ici est que le contrat d'interface avec une superclasse n'est ni typé ni contrôlé, ce qui fait trébucher les développeurs et ce serait formidable si TS ou l'outillage pouvait aider.

@armandn Je ne pense pas que notre manque de fermeture ou la gentillesse de notre suggestion est ce qui maintient la demande rejetée, mais les différences entre la sémantique C# et TS :

Remplacement de la méthode de base C# :

  • Nécessite le mot-clé override
  • Crée un nouvel enregistrement sur VTable
  • Vérifie les résumés/virtuels obligatoires dans la méthode de base
  • Vérifie l'identité de la signature
  • IDE : Déclenche la saisie semi-automatique après l'écriture d'un remplacement

Implémentation de l'interface C# :

  • Aucun mot-clé nécessaire, mais implémentation explicite de l'interface possible en utilisant le nom de l'interface.
  • Créez un nouvel enregistrement d'interface dans la VTable.
  • Vérifie l'identité de la signature
  • IDE : QuickFix pour implémenter l'interface / implémenter l'interface explicitement.

Ainsi, le comportement en C # est assez différent selon que vous posez des questions sur les classes ou les interfaces, mais dans tous les cas, vous obtenez les trois principaux avantages :

  1. Vérification de signature identique
  2. Vérifier que la méthode peut/doit être remplacée (faute d'orthographe dans le nom de la méthode)
  3. Prise en charge de l'IDE pour l'écriture de la fonction

Avec une sémantique légèrement différente, nous avons déjà 1) dans TS, malheureusement 2) et 3) manquent. La raison pour laquelle override a été rejeté, à mon avis, est qu'une syntaxe similaire suppose un comportement similaire, et ce n'est pas souhaitable car :

Une classe avec 5 méthodes, dont 3 sont marquées override, n'impliquerait pas que les 2 autres ne soient pas overrides.

Il se trouve que nous avons déjà 1) 2) et 3) lors de l'écriture de _littéraux d'objet_, mais pas lors de l'écriture de membres de classe qui étendent d'autres classes ou implémentent des interfaces.

Dans cet esprit, je pense que nous pouvons tous être d'accord sur cette sémantique:

  • Le _keyword_ doit être suffisamment ambigu pour éviter le problème du "monde divisé". (c'est-à-dire : check ou super. , ou MyInterface. )
  • Aucune vérification de abstract/virtual mais vérification de l'existence d'un membre dans la classe de base/interfaces implémentées. (Avantage 2)
  • Vérifie la compatibilité de signature suffisamment similaire à celle que TS fait jusqu'à présent. (Avantage 1)
  • IDE : déclenche une saisie semi-automatique après le mot-clé. (Avantage 3)

De plus, je pense que ces deux éléments sont nécessaires pour l'exhaustivité de la solution * :

  • Cela devrait fonctionner pour Classes et Interfaces , car TS est principalement basé sur l'interface et les classes sont un raccourci pour l'héritage prototypique. Nécessaire compte tenu des méthodes d'interface facultatives.
  • Cela devrait fonctionner pour Methods et Fields , car les méthodes ne sont que des fonctions résidant dans un champ.

Ces deux points sont utiles dans le cas d'utilisation très réel de l'implémentation du composant React :

``` C#
classe MonComposant{
implements.state = {/ auto-complétion pour MyComponentState ici /} ;

implements.componentWillMount(){
    //do my stuff
}

}
```

La solution basée sur les annotations de @RyanCavanaugh n'est pas suffisante car :

  • Cela ne fonctionnera pas avec les interfaces.
  • Cela ne fonctionnera pas avec les champs.
  • En supposant une infrastructure de type Roslyn pour aider avec l'outillage, il n'y a aucun moyen d'ajouter un QuickFix après avoir écrit une liste de saisie semi-automatique après avoir écrit @override .

Compte tenu de la sémantique, il s'agit simplement de choisir la bonne syntaxe. Voici quelques alternatives :

  • override componentWillMount() : Intuitif mais trompeur.
  • check componentWillMount() : explicite mais consommateur de mots-clés.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

Des avis?

@olmobrutall bon résumé. Quelques points :

Une option de mot-clé (extraite de C#) serait nouvelle - indiquant un nouvel emplacement :

class MyComp extends React.Component<IProps,IState> {
...
    new componentWillMount() { ... }
    componentWillMount() { ...} // would compile, maybe unless strict mode is enabled
    new componentwillmount() { ... } <-- error

Mon autre point est la question de la nature obligatoire de l'utilisation de ce qui précède. En tant que contrat entre la super classe parente et la classe dérivée, il est logique de spécifier quels sont les points d'interface où la syntaxe ci-dessus serait valide. Ce sont vraiment des points d'extension internes pour la super classe, donc quelque chose comme :

class Component<P,S> {
    extendable componentWillMount() {...}
}

La même chose s'appliquerait à l'interface aussi.

Merci :)

Et si vous écriviez simplement this ?

class MyComponent<MyComponentProps, MyComponentState> {
    this.state = {/*auto-completion for MyComponentState here*/};

    this.componentWillMount(){
        //do my stuff
    }
}

À propos extendable , il y a déjà abstract dans TS 1.6, et l'ajout de virtual créera peut-être à nouveau le problème du monde divisé ?

Bon, j'ai pensé à abstrait mais il peut y avoir une implémentation dans la super classe, donc ça n'a pas vraiment de sens. De même avec virtual , car cela impliquerait que les membres non virtuels ne sont pas virtuels - ce qui est trompeur.

this. fonctionne, je suppose que vous pourriez même avoir (sous forme longue):

   this.componentWillMount = () => { }

Le seul problème avec ceci est qu'il ne devrait être limité qu'aux points d'extension réservés, pas à tous les membres de la classe de base.

que se passe-t-il...

TypeScript n'est pas et n'a jamais été la version javascript de C#. La raison n'est donc pas que la fonctionnalité suggérée diffère de la sémantique de C#. Les raisons de la fermeture telles qu'énoncées par Ryan puis clarifiées par Daniel R étaient

Comme Ryan l'a expliqué, le problème est que marquer une méthode comme un remplacement n'implique pas qu'une autre méthode n'est pas un remplacement. La seule façon dont cela aurait du sens est si tous les remplacements devaient être marqués avec un mot-clé de remplacement (ce qui, si nous l'imposions, serait un changement de rupture).

Pourtant, vous persistez toujours avec vos problèmes de saisie semi-automatique. La saisie semi-automatique n'a pas besoin d'un nouveau mot-clé dans la langue pour vous fournir des suggestions améliorées.

La raison pour laquelle ce thread existe était d'obtenir des erreurs de compilation lorsqu'une fonction est remplacée illégalement ou lorsqu'une méthode a été déclarée comme une substitution mais n'a pas réellement remplacé une fonction dans une classe de base. C'est un concept simple et bien défini dans de nombreuses langues qui prend également en charge la plupart, sinon plus, des fonctionnalités linguistiques que le texte dactylographié a à offrir. Il n'a pas besoin de résoudre tous les problèmes du monde, il doit juste être le mot-clé override, pour les overrides.

Plus de personnes ont depuis manifesté leur intérêt pour la proposition originale, alors restons-en au sujet pour lequel le problème a été soulevé et soulevons de nouveaux problèmes pour de nouvelles idées.

TypeScript n'est pas et n'a jamais été la version javascript de C#.

Je le compare avec C # comme langage de référence, car je suppose que c'est le comportement que vous supposez.

La saisie semi-automatique n'a pas besoin d'un nouveau mot-clé dans la langue pour vous fournir des suggestions améliorées.

Comment suggérez-vous qu'il soit déclenché alors? Si vous affichez une liste déroulante de saisie semi-automatique lors de l'écriture d'un nom aléatoire dans un contexte de classe, cela sera très ennuyeux lorsque nous voulons simplement déclarer un nouveau champ ou une nouvelle méthode.

La raison pour laquelle ce thread existe était d'obtenir des erreurs de compilation lorsqu'une fonction est remplacée illégalement ou lorsqu'une méthode a été déclarée comme une substitution mais n'a pas réellement remplacé une fonction dans une classe de base.

J'ai absolument inclus ce cas d'utilisation, sous Benefit 2 .

Il n'a pas besoin de résoudre tous les problèmes du monde, il doit juste être le mot-clé override, pour les overrides.

Donc, votre suggestion est de corriger _une étape à la fois_ au lieu de reculer d'une étape et d'examiner des problèmes similaires/connexes ?. C'est peut-être une bonne idée pour votre Scrum Board mais pas pour la conception de langages.

La raison pour laquelle ils sont prudents quant à l'ajout du mot-clé est qu'ils ne peuvent pas supprimer les fonctionnalités d'une langue.

Quelques erreurs de conception en C# à cause d'un manque de complétion :

  • Co-variance / contra-variance de tableau non sécurisé.
  • var ne fonctionne que pour les types de variables, pas pour les paramètres génériques automatiques ou les types de retour. Peut-être auto sera un meilleur mot clé comme en C++ ?

Essayez simplement d'utiliser React pendant une seconde et vous verrez l'autre côté de l'image.

Remplacer une méthode qui a déjà été implémentée dans une classe de base et implémenter une méthode d'interface sont _ deux choses complètement différentes _. Alors oui, ce qui est suggéré est de corriger l'un de ces scénarios avec un mot-clé dédié au lieu d'essayer de trouver un mot-clé de l'armée suisse.

À quoi bon un mot-clé qui peut signifier l'une des 3 choses différentes pour les développeurs qui lisent du code pour la première fois ? C'est ambigu et déroutant, surtout si vous parlez d'utiliser un mot-clé comme this qui fait déjà d'autres choses (totalement sans rapport !) dans la langue - ça ne pourrait pas être plus générique, c'est presque inutile.

Si votre principale préoccupation est la saisie semi-automatique, les éditeurs ont suffisamment d'informations _maintenant_ pour pouvoir suggérer des méthodes à partir des classes de base et des interfaces implémentées "au fur et à mesure que vous tapez".

Remplacer une méthode qui a déjà été implémentée dans une classe de base et implémenter une méthode d'interface sont deux choses complètement différentes.

Dans le cas général oui, mais il ne s'agit pas d'implémenter une quelconque méthode d'interface. Nous parlons d'une méthode d'interface facultative _où la classe parent implémente l'interface_. Dans ce cas, vous pouvez dire que 1) l'interface permet à la méthode d'être implémentée en tant que undefined , 2) la classe parent a une implémentation indéfinie et 3) la classe enfant remplace l'implémentation indéfinie par une implémentation de méthode.

@olmobrutall Je pense que votre commentaire sur la conception de langages et sur le fait qu'il ne s'agit pas d'un tableau de mêlée est un peu intéressé. J'ai vu environ quatre mises à jour de TS en moins d'un an.

Si la conception du langage était aussi bien considérée que vous le suggérez, alors il y aurait déjà un document de spécification de langage nous indiquant exactement comment les remplacements devraient fonctionner, et nous n'aurions peut-être même pas cette conversation.

Je ne fais pas ce commentaire avec un quelconque dénigrement des développeurs/concepteurs de TS, car TS est déjà excellent et je redouterais d'avoir à utiliser du JS standard à la place.

Oui TS n'est pas C# et ce n'est pas C++. Mais un certain nombre de langages ont choisi le mot-clé override pour répondre aux objectifs discutés ici, il semble donc contre-productif de suggérer une syntaxe totalement étrangère.

Le principal problème semble être de ne pas vouloir introduire de changement radical. La réponse simple est un indicateur de compilateur, fin de l'histoire. Pour certaines personnes comme moi, un mot clé de remplacement facultatif est inutile. Pour d'autres, ils veulent embellir leur code progressivement. L'indicateur de compilateur résout le dilemme.

Les différences de signature sont une conversation différente. Le nouveau mot-clé semble inutile car JS ne peut pas prendre en charge plusieurs méthodes du même nom (à moins que TS ne crée des noms mutilés dérivés de la signature à la C++, ce qui est hautement improbable).

J'ai vu environ quatre mises à jour de TS en moins d'un an.

Je ne veux pas dire que vous ne pouvez pas être rapide et itérer rapidement. Je suis aussi heureux que quiconque que ES6 et TS évoluent rapidement. Ce que je veux dire, c'est qu'il faut essayer de prédire l'avenir, pour éviter de mettre le langage dans une impasse.

Je pourrais accepter d'utiliser le mot-clé override . Avec des arguments appropriés, même en gardant les champs et les interfaces hors de portée, mais je ne peux pas être d'accord avec l'argument "_restons concentrés et résolvons juste ce problème particulier comme les autres langages le font sans trop réfléchir_".

Mais un certain nombre de langages ont choisi le mot-clé override pour répondre aux objectifs discutés ici, il semble donc contre-productif de suggérer une syntaxe totalement étrangère.

Aucun de ces langages n'a d'héritage prototypique ou de méthode optionnelle (des méthodes qui ne sont ni abstraites ni virtuelles, elles pourraient simplement _ne pas exister_ au moment de l'exécution), et ce sont des problèmes connexes qui doivent être discutés (et peut-être ignorés) avant de s'engager.

En d'autres termes : disons que nous faisons ce que vous semblez suggérer et que nous implémentons le remplacement sans trop réfléchir. Ensuite, moi, ou toute autre personne utilisant TSX, ajoute un problème expliquant pourquoi override ne fonctionne pas avec les composants React. Quel est ton plan?

Dans le cas général oui, mais il ne s'agit pas d'implémenter une quelconque méthode d'interface. Nous parlons d'une méthode d'interface facultative où la classe parent implémente l'interface.

Peu importe où l'interface a été configurée, le fait est qu'elles ne sont pas la même chose et ne doivent donc pas partager un mot-clé car l'_intention_ du programme n'est pas claire.

Vous pouvez, par exemple, remplacer une méthode qui a été implémentée pour la conformité de l'interface dans une classe de base ; Si nous devions mettre tous nos oeufs dans un mot-clé pour ces deux choses différentes, comment quelqu'un saurait-il alors s'il s'agit de la déclaration initiale de cette fonction ou d'une substitution à celle précédemment définie dans une classe de base ? Vous ne le feriez pas, et il ne serait pas possible de le savoir sans une inspection plus approfondie de la classe de base - qui peut même se trouver dans un fichier tiers .d.ts , ce qui en fait un cauchemar absolu à trouver, selon la profondeur dans la chaîne d'héritage, la fonction a été initialement implémentée.

En d'autres termes : disons que nous faisons ce que vous semblez suggérer et que nous implémentons le remplacement sans trop réfléchir. Ensuite, moi, ou toute autre personne utilisant TSX, ajoute un problème expliquant pourquoi le remplacement ne fonctionne pas avec les composants React. Quel est ton plan?

Pourquoi faut-il corriger React ? Si React a un problème différent de ce que cela essaie de résoudre, je ne peux pas comprendre pourquoi override doit le résoudre ? Avez-vous essayé d'ouvrir un autre problème pour suggérer que quelque chose soit fait concernant la mise en œuvre de l'interface ?

Je ne serais pas d'accord pour dire qu'on n'a pas suffisamment réfléchi à cela. Nous suggérons une technique éprouvée qui a réussi dans toutes les autres langues auxquelles je peux penser et qui l'ont implémentée.

le fait est qu'ils ne sont pas la même chose et ne doivent donc pas partager un mot-clé car l'intention du programme n'est pas claire.

Ne sont pas? Regardez ces deux définitions alternatives de BaseClass

class BaseClass {
     abstract myMethod(); 
}
interface ISomeInterface {
     myMethod?(); 
}

class BaseClass extends ISomeInterface {
}

Et puis dans ton code tu fais :

``` C#
class BétonClasse {
remplacer maMéthode() {
// Faire des choses
}
}

You think it should work in just one case and not in the other? The effect is going to be 100% identical in Javascript (creating a new method in ConcreteClass prototype), from the external interface and from the tooling perspective. 

Even more, maybe you want to capture `this` inside of the method, implementing it with a lambda (useful for React event handling). In this case you'll write something like this:

``` C#
class ConcreteClass {
    override myMethod = () => { 
         // Do stuff
    }
}

Le comportement sera à nouveau identique si la méthode est abstraite ou provient de l'interface : ajouter un champ dans la classe avec un lambda l'implémentant. Mais cela semble un peu étrange de override un champ, puisque vous ne faites qu'attribuer une valeur.

Ne voyons pas cela en utilisant super. (ma syntaxe préférée en ce moment, mais je suis ouvert aux alternatives).

``` C#
class BétonClasse {
super.myMethod() { //méthode dans le prototype
// Faire des choses
}

super.myMethod = () => {  //method in lambda
     // Do stuff
}

}
```

Maintenant, le concept est conceptuellement plus simple : ma super classe dit qu'il existe une méthode ou un champ et la ConcreteClass peut le définir dans le prototype / l'assigner / le lire / l'appeler.

Pourquoi faut-il corriger React ?

N'est-ce pas juste réagir, regardez angulaire:

  • React : 58 interfaces et 3 classes.
  • Angulaire : 108 interfaces et 11 classes.

Bien sûr, la plupart des interfaces ne sont pas destinées à être implémentées, ni toutes les classes à remplacer, mais une chose est claire : dans Typescript, les interfaces sont plus importantes que les classes.

Avez-vous essayé d'ouvrir un autre problème pour suggérer que quelque chose soit fait concernant la mise en œuvre de l'interface ?

Comment dois-je l'appeler ? override pour l'interface et les champs ?

Nous suggérons une technique éprouvée qui a réussi dans toutes les autres langues auxquelles je peux penser.

Les langues que vous avez en tête sont assez différentes. Ils ont un héritage basé sur une VTable statique.

Dans Typescript, une classe n'est qu'une interface + un prototype d'héritage automatique de méthodes. Et les méthodes ne sont que des champs avec une fonction à l'intérieur.

Afin d'adapter la fonctionnalité override à TS, ces deux différences fondamentales doivent être prises en compte.

S'il vous plaît @kungfusheep faites l'effort de réfléchir à une solution à mon problème. Si vous souhaitez ajouter différents mots-clés et les mettre en œuvre dans une deuxième étape, c'est OK, mais prenez une seconde pour imaginer comment cela devrait être.

Je ne vois pas d'autre façon de dire ce que j'ai déjà dit. Ils ne sont pas les mêmes. Ils sont similaires, mais pas identiques. Veuillez consulter ce commentaire de l'un des développeurs TS RE : le mot-clé readonly - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753 - qui renforce ce que je dis.

Je suis d'accord avec l'idée générale, mais voyons en pratique :

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Cela ressemble à VB ou Cobol, n'est-ce pas ?

Il semble que cela ait du sens, au moins.

Considérez cet exemple, s'il n'y avait que le mot-clé override (ou un seul).

interface IDo {
    do?() : void;
}
class Component implements IDo {
    protected commitState() : void {
        /// do something
    }
    override public do() : void {
        /// base implements 'do' in this case
    }
}

Maintenant, implémentons notre propre composant en utilisant ce que nous venons d'écrire.

class MyComponent extends Component {
    override protected commitState(){
        /// do our own thing here
        super.commitState();
    }
    override do() : void {
        /// this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing. 
    }

}

Une façon de savoir serait le type de super :

  override do() : void {
        super.do(); // this compiles, if it was an interface then super wouldn't support `do`
    }

exactement! ce qui signifie que la conception est mauvaise. il ne devrait pas y avoir d'enquête sur le code d'écrémage, cela devrait simplement avoir un sens lorsque vous le lisez.

c'est ambigu. Est-ce que je l'implémente à partir d'une interface ou que je remplace une méthode de base ? Je n'ai aucun moyen de savoir.

Quelle est la différence dans la pratique ? L'objet n'a qu'un seul espace de noms et vous ne pouvez placer qu'un seul lambda dans le champ do . Il n'y a aucun moyen d'implémenter explicitement une interface de toute façon. La mise en œuvre va être :

MyComponent.prototype.do = function (){
    //your stuff
}

indépendamment de ce que vous écrivez.

Peu importe la sortie. Vous pouvez remplacer intentionnellement ou non certaines fonctionnalités dans une classe de base, mais il n'y a pas d'intention dans un mot clé qui est ambigu.

Quelle erreur ou comportement inattendu sera résolu en ayant deux mots-clés ?

Allez maintenant mon pote. Vous êtes manifestement un type intelligent. Je viens de dire "remplacer involontairement certaines fonctionnalités d'une classe de base" ; ne pouvons-nous pas en déduire un comportement inattendu qui aurait pu potentiellement se produire ?

Pour être clair, je ne propose pas de transformer ce problème en une proposition pour deux mots-clés. Ce problème concerne le mot-clé override - tout le reste nécessitera une nouvelle proposition et sa propre discussion sur la sémantique.

Pour être clair, je ne propose pas de transformer ce problème en une proposition pour deux mots-clés. Ce problème concerne le mot-clé override - tout le reste nécessitera une nouvelle proposition et sa propre discussion sur la sémantique.

Peu importe le nombre de questions dont il faut discuter, ou de qui vient l'idée. Vous proposez de scinder deux idées très liées et ne considérez même pas une syntaxe cohérente.

Les arguments pour savoir si nous avons besoin de 1, 2 ou 3 mots-clés appartiennent à ce fil et ne sont pas terminés (... mais deviennent répétitifs). Ensuite, nous pourrons peut-être discuter de la syntaxe dans un autre fil (car la sémantique sera de toute façon identique :P).

Dans mon exemple :

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Ne faites pas assign , implement et override exactement la même chose : Vérifiez que le nom existe (dans la classe de base, les interfaces implémentées, les interfaces implémentées par la base classe, etc...).

S'il y a un conflit de nom entre les classes de base et certaines interfaces implémentées, vous obtiendrez une erreur de compilation, que ce soit avec 1, 2 ou aucun mot-clé.

Pensez également à l'objet littéral :

var mc = new MyComponent(); 
mc.state = null;
mc.componentDidMount =null;
mc.render = null;

Avec exactement la même syntaxe, je peux réaffecter des champs ou une méthode indépendamment s'ils proviennent de la classe de base, des implémentations d'interface directe ou des interfaces implémentées dans la classe de base.

Don't assign, implement et override font exactement la même chose : Vérifier que le nom existe (dans la classe de base, les interfaces implémentées, les interfaces implémentées par la classe de base, etc...).

Vous venez de décrire 3 scénarios différents là-bas, donc évidemment ils ne sont pas les mêmes. J'ai le sentiment que je pourrais décrire pourquoi ils sont différents de vous toute la journée et que vous seriez toujours assis à argumenter qu'ils ne le sont pas, alors je vais me retirer de cette ligne de discussion particulière pour le moment. Il n'y a rien à dire que les gars de TS envisagent encore cela pour le moment de toute façon.

Avec la fermeture de # 6118, je pense qu'il y a lieu de discuter si les problèmes là-bas et les problèmes ici peuvent être résolus simultanément.

Je n'étais pas au courant de https://github.com/Microsoft/TypeScript/pull/6118. L'idée ressemble à une alternative possible à l'ajout override .

Si j'ai bien compris le problème, le problème est que vous pouvez avoir plusieurs classes/interfaces de base avec des déclarations de membres compatibles mais non identiques, et elles doivent être unifiées lors de leur initialisation dans la classe enfant sans type.

Sans avoir une bonne idée des conséquences, je serais heureux si :

  • le membre produira une erreur de compilation qui nécessite une déclaration de type explicite sur la classe enfant
  • le membre prendra l'intersection de tous les types possibles.

L'OMI le plus important serait d'avoir un moyen de déclencher l'auto-complétion lors de l'écriture des membres de la classe (Ctrl + Espace). Lorsque le curseur est directement à l'intérieur d'une classe, vous pouvez définir de nouveaux membres ou redéfinir ceux hérités, de sorte que l'auto-complétion ne peut pas être trop agressive, mais avec un déclenchement manuel, le comportement devrait être correct.

Concernant les commentaires de @RyanCavanaugh :

Nous comprenons parfaitement les cas d'utilisation ici. Le problème est que l'ajouter à ce stade du langage ajoute plus de confusion qu'il n'en supprime. Une classe avec 5 méthodes, dont 3 sont marquées override, n'impliquerait pas que les 2 autres ne soient pas overrides. Pour justifier son existence, le modificateur aurait vraiment besoin de diviser le monde plus proprement que cela.

Taper une variable comme any n'implique pas qu'une autre variable est ou n'est pas aussi any . Mais, il existe un compilateur plat --no-implicit-any pour imposer que nous le déclarions explicitement. Nous pourrions également avoir un --no-implicit-override , que j'activerais pour ma part s'il était disponible.

L'utilisation d'un mot-clé override donne aux développeurs une énorme quantité d'informations lors de la lecture de code avec lequel ils ne sont pas familiers, et la possibilité de l'appliquer donnerait un contrôle supplémentaire du temps de compilation.

Pour tous les +1ers -- pouvez-vous parler de ce qui n'est pas suffisant dans la solution de décorateur présentée ci-dessus ?

Existe-t-il un moyen pour un décorateur d'être une meilleure solution qu'un mot clé de remplacement ? Il y a plusieurs raisons pour lesquelles c'est pire : 1) Cela ajoute une surcharge d'exécution, même minime ; 2) Ce n'est pas une vérification du temps de compilation ; 3) Je dois inclure ce code dans chacune de mes bibliothèques ; 4) Il n'y a aucun moyen pour cela d'attraper les fonctions auxquelles il manque le mot-clé override.

Donnons un exemple. J'ai une bibliothèque avec trois classes ChildA , ChildB , Base . J'ai ajouté une méthode doSomething() à ChildA et ChildB . Après quelques refactorisations, j'ai ajouté une logique supplémentaire, optimisé et déplacé doSomething() vers la classe Base . En attendant, j'ai un autre projet qui dépend de ma bibliothèque. Là j'ai ChildC avec doSomething() . Il n'y a aucun moyen, lorsque je mets à jour mes dépendances, de découvrir que ChildC remplace maintenant implicitement doSomething() , mais d'une manière non optimisée qui manque également certaines vérifications. C'est pourquoi un décorateur @overrides ne suffira jamais.

Nous avons besoin d'un mot-clé override et d'un indicateur de compilateur --no-implicit-override .

Un mot-clé override m'aiderait beaucoup puisque j'utilise dans mon projet une simple hiérarchie de classes de base pour créer tous mes composants. Mon problème réside dans le fait que ces composants peuvent avoir besoin de déclarer une méthode à utiliser ailleurs, et cette méthode peut ou non être définie dans une classe parent et peut ou non déjà faire des choses dont j'ai besoin.

Par exemple, disons qu'une fonction validate prend une classe avec une méthode getValue() comme paramètre. Pour construire cette classe, je peux hériter d'une autre classe qui pourrait déjà définir cette méthode getValue() , mais je ne peux pas vraiment le savoir à moins de regarder son code source. Ce que je ferais instinctivement, c'est implémenter cette méthode dans ma propre classe, et tant que la signature est correcte, personne ne me dira rien.

Mais peut-être que je n'aurais pas dû faire ça. Les possibilités suivantes supposent toutes que j'ai effectué un remplacement implicite :

  1. La classe de base a déjà défini cette méthode comme moi, j'ai donc écrit une fonction pour rien. Pas vraiment un problème.
  2. J'ai mal réécrit la fonction, la plupart du temps parce que j'ai oublié de faire des choses non évidentes qui étaient déjà gérées par la classe de base. Ce que j'avais vraiment besoin de faire ici, c'est d'appeler super dans mon remplacement, mais personne ne m'a suggéré de le faire.

Avoir un mot-clé de remplacement obligatoire m'aurait dit "hé, vous remplacez, alors vous devriez peut-être vérifier à quoi ressemble la méthode d'origine avant de faire votre travail". Cela améliorerait grandement mon expérience avec l'héritage.

Bien sûr, comme suggéré, il devrait être placé sous un drapeau --no-implicit-override car ce serait un changement radical et que la plupart des gens ne se soucient pas tellement de tout cela.

J'aime la comparaison @eggers faite avec any et --no-implicit-any car c'est le même genre d'annotation et cela fonctionnerait exactement de la même manière.

@olmobrutall Je regardais certaines de vos discussions sur le remplacement des méthodes abstraites et des méthodes d'interface.

Si mon avis, override implique l'existence d'un super. Ni les méthodes abstraites ni les méthodes définies dans une interface ne peuvent être appelées via un appel super. , et ne devraient donc pas être remplaçables ( il n'y a rien à remplacer). Au lieu de cela, si nous faisons quelque chose de plus explicite pour ces cas, cela devrait être un mot-clé implements . Mais ce serait une discussion de fonctionnalité distincte.

Voici les problèmes tels que je les vois, classés du plus important au moins important. Ai-je manqué quelque chose?

Défaut de passer outre

Il est facile de _penser_ que vous redéfinissez une méthode de classe de base alors que vous ne le faites pas :

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFileName(f: string) { return false; }
}

C'est probablement le plus gros problème.

Défaut de mise en œuvre

Ceci est très étroitement lié, en particulier lorsque l'interface implémentée a des propriétés optionnelles :

interface NeatMethods {
  hasFilename?(f: string): boolean;
}
class Mine implements NeatMethods {
  // oops
  hasFileName(f: string) { return false; }
}

C'est un problème moins important que _failure to override_, mais c'est quand même mauvais.

Remplacement accidentel

Il est possible de remplacer une méthode de classe de base sans s'en rendre compte

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // I didn't know there was a base method with this name, so oops?
  hasFilename(f: string) { return true; }
}

Cela devrait être relativement rare simplement parce que l'espace des noms de méthodes possibles est très grand et que les chances que vous écriviez également une signature compatible _et_ ne signifient pas avoir fait cela en premier lieu sont faibles.

Accidentel any s

Il est facile de penser que les méthodes override obtiendront les types de paramètres de leur base :

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFilename(f) { return f.lentgh > 0; }
}

Nous avons essayé de saisir ces paramètres automatiquement, mais nous avons rencontré des problèmes. Si hasFilename était explicitement marqué override , nous pourrions peut-être résoudre ces problèmes plus facilement.

Lisibilité

Il n'est pas immédiatement clair quand une méthode remplace une méthode de classe de base et quand ce n'est pas le cas. Cela semble être un problème mineur car il existe aujourd'hui des solutions de contournement raisonnables (commentaires, décorateurs).

Expérience d'éditeur

Vous devez taper manuellement les noms des méthodes de base lors de l'écriture des méthodes de substitution de classe dérivée, ce qui est ennuyeux. Nous pourrions facilement résoudre ce problème en fournissant toujours ces noms dans la liste d'achèvement (je pense que nous avons déjà un bogue à ce sujet), nous n'avons donc pas vraiment besoin d'une fonctionnalité de langage pour résoudre ce problème.

Non-problèmes

Les énumérer comme des choses qui appartiennent à un problème distinct ou qui ne se produiront tout simplement pas :

  • Correspondance exacte de la signature - ce n'est pas quelque chose qui devrait être mélangé avec override , et il a vraiment une sémantique indésirable dans de nombreux bons scénarios
  • Syntaxe insuffisamment exotique (par exemple implements.foo = ... ). Pas besoin de remise à vélos ici

@RyanCavanaugh Je suis également d'accord avec tout ce qui précède à propos de cet ordre. En guise de commentaire, je ne pense pas que le mot-clé override devrait résoudre le problème de "l'échec de la mise en œuvre". Comme je l'ai dit plus haut, je pense que ce problème devrait être résolu par un mot-clé différent (par exemple implements ) dans le cadre d'un ticket différent. ( override devrait impliquer une méthode super. )

@RyanCavanaugh Je suis d'accord avec tous les points mais je ne vois aucune référence au problème également très lié des champs initialisés déclarés dans un type parent sans remplacer le type (puis perdre la vérification du type). Je pense que la fonctionnalité ne devrait pas se limiter aux déclarations de méthodes dans un langage où les méthodes ne sont que des fonctions dans des champs d'objets.

@eggers même si à la fin deux mots-clés sont nécessaires, la sémantique sera très similaire et je pense que les fonctionnalités doivent être discutées ensemble.

override devrait impliquer un super. méthode

En C#, le remplacement peut (et doit) être utilisé également pour les méthodes abstraites (sans .super). En Java , @override est un attribut. Bien sûr, ils sont dans un langage différent, mais avec des mots-clés similaires, vous vous attendez à des comportements similaires.

Je suis d'accord avec les points de @RyanCavanaugh , même si je dirais que le problème de "remplacement accidentel" pourrait être un peu plus courant qu'il ne le croit (surtout lorsqu'il s'agit d'étendre une classe qui étend déjà autre chose, comme un composant React qui serait déjà définir une méthode componentWillMount dans la première extension).

Je pense cependant, comme @eggers l' a dit, que le problème de "l'échec de l'implémentation" est quelque chose d'assez différent, et il serait étrange d'utiliser un mot-clé override pour une méthode qui n'existe pas dans une classe parent. Je sais que ce sont des langages différents avec des problématiques différentes, mais en C # override n'est pas utilisé pour les interfaces. Je suggérerais, si nous pouvions obtenir un indicateur --no-implicit-override , que les méthodes d'interface devraient être préfixées avec le nom de l'interface (ce qui ressemblerait beaucoup à C# et serait donc plus naturel).

Quelque chose comme

interface IBase {
    method1?(): void
}

class Base {
    method2() { return true; }
}

class Test extends Base implements IBase {
    IBase.method1() { }
    override method2() { return true; }
}

Je pense que @RyanCavanaugh énumère les problèmes fondamentaux. Je compléterais cela avec "Quels sont les désaccords":

  • La déclaration d'une méthode pour correspondre à une interface est-elle considérée comme un override ? Par exemple:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • déclare une propriété pour correspondre à une interface considérée comme un override ?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • Y a-t-il un besoin pour une sorte de mot-clé implements , pour gérer les cas ci-dessus au lieu d'un mot-clé override , et la forme que cela prendrait.

@ kevmeister68 merci d'avoir explicitement indiqué les désaccords.

Une chose : vous devez rendre les membres de l'interface facultatifs pour vraiment montrer le problème, comme ceci le compilateur dactylographié se plaindra lorsqu'un champ ou une méthode n'est pas implémenté, résolvant "Échec d'implémentation".

@olmobrutall Merci pour cela. Je n'ai jamais déclaré une méthode facultative - est-ce possible (je viens d'un milieu C # et il n'y a pas un tel concept)? J'ai mis à jour les exemples que j'ai énumérés ci-dessus pour changer les variables membres en facultatif - est-ce mieux ?

@ kevmeister68 également la méthode draw dans IDrawable :)

@RyanCavanaugh

Syntaxe insuffisamment exotique (ex. implements.foo = ...). Pas besoin de remise à vélos ici

Merci de m'avoir appris une nouvelle expression . Je ne vois pas beaucoup de problèmes dans la sémantique de la fonctionnalité :

  • Nous voulons tous des erreurs de compilation si la méthode n'existe pas
  • Nous voulons tous des erreurs de compilation dont le type ne correspond pas.
  • Nous voulons également que les paramètres soient implicitement typés sur le parent, comme le font les expressions lambda.
  • Nous avons besoin d'aide de l'IDE pour écrire correctement la méthode.

La plupart des problèmes reposent sur la syntaxe et les comportements qu'ils impliquent :

  • Membres facultatifs dans Interface vs membres dans les classes de base
  • Méthodes vs champs (je ne sais pas où seront les lambdas)

Comparons l'arbre des syntaxes les plus prometteuses :

outrepasser / mettre en œuvre

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     implements fly() {/*...*/}
     altitude = 1000; //what goes here?
}

Avantages :

  • C'est naturel pour les programmeurs ayant une formation OO.
  • Se sent bien en comparant avec extends / implements .

Désavantages:

  • Ne fournit pas de solution au problème de champ, aucun override ou implements ne semble correct. Peut-être super. , mais alors qu'est-ce qu'on fait avec les interfaces ?
  • Si les méthodes sont remplacées à l'aide d'un lambda (utile pour capturer ceci) d'un function() , les mêmes problèmes se produisent.

remplacer / InterfaceName.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     ICanFly.fly() {/*...*/}
     ICanFly.altitude = 1000; //what goes here?
}

Avantages :

  • Est également naturel pour les programmeurs ayant une formation OO.
  • Résout le problème de champ pour les interfaces, mais pas pour les classes, mais nous pourrions utiliser super. pour elles.

Désavantages:

  • Les noms d'interface peuvent être longs, en particulier avec les génériques, et afficher la liste de compétition automatique sera également problématique, nécessitant un Alt + Espace pour le rendre explicite.
  • Donne l'impression que vous pouvez implémenter indépendamment deux membres portant le même nom à partir d'interfaces différentes. Cela fonctionne en C # mais ne fonctionnera pas en Javascript.

this.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();

     this.talk(){/*...*/}
     this.walk = () => {/* force 'this' to be captured*/} 

     this.fly() {/*...*/}
     this.altitude = 1000;
}

Avantages :

  • Résout les classes, les interfaces, les méthodes et les champs.
  • Utilise (abuse ?) un seul mot-clé préexistant.
  • Utiliser this. pour déclencher l'auto-complétion semble naturel.
  • Indique clairement que l'objet n'est qu'un espace de noms et que vous ne pouvez avoir qu'une seule chose pour chaque nom.

Désavantages:

  • Ressemble à une expression, pas à une déclaration, ce qui la rend difficile à analyser pour des yeux non avertis.

Alors que l'échec de l'implémentation d'une méthode d'interface non facultative entraînera une erreur du compilateur, si l'interface change à un moment donné dans le futur (disons qu'une méthode est supprimée), il n'y aura pas d'avertissement pour toutes les classes qui ont implémenté cette méthode. Ce n'est peut-être pas un problème aussi important que les options facultatives, mais je pense qu'il est juste de dire que la portée de cela les dépasse.

Cependant, je continue de croire que override est une chose nettement différente et que le fait d'avoir potentiellement deux mots-clés plutôt qu'un seul permettrait de mieux transmettre l'intention du programme.

Par exemple, considérez un scénario où un développeur _croit_ qu'il implémente une méthode d'interface alors qu'en fait il remplace la méthode qui a déjà été déclarée dans une classe de base. Avec deux mots-clés, le compilateur peut empêcher ce genre d'erreur et lancer une erreur sur le ton method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

class Base implements IDelegate {
    implement public execute() : void { /// fine, this is correctly implementing execute
    }
}

class Derived extends Base {
    implement public execute() : void { 
/// ERROR: `method "execute():void" already implemented in a base class`
    }
}

Je ne sais pas si cela est pertinent dans JS, mais les champs de classe sont censés être privés dans OO, donc je ne pense pas que nous ayons besoin de remplacer explicitement les champs puisque le fait qu'ils soient privés nous empêche déjà de remplacer complètement .

Les méthodes d'instance sont cependant des champs, et d'après ce que je comprends, beaucoup de gens les utilisent à la place des méthodes prototypes. À propos de ça,

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

est une erreur du compilateur, car walk est une méthode prototype dans Person et une méthode d'instance dans SuperMan .

Quoi qu'il en soit, je ne suis pas sûr que override conviendrait ici, car nous ne remplaçons pas les champs. Encore une fois, cela ressemblerait à C #, mais je préfère utiliser le mot-clé new au lieu de override ici. Parce que ce n'est pas la même chose qu'un remplacement de méthode (je peux appeler super.myMethod dans un remplacement, pas ici).

Ma solution préférée serait alors quelque chose comme (en supposant que nous sommes en mode de remplacement strict):

class Person{
    dateOfBirth: Date;
    talk() { }
    walk = () => { }
}

interface ICanFly {
    fly?();
    altitude?: number;
}

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { }
     new walk = () => { }

     implements fly() {/*...*/}
     implements altitude = 1000;
}

Ma principale préoccupation concerne les interfaces. Nous ne devrions pas avoir à écrire implements pour les membres d'interface non optionnels, car nous serons déjà pris par le compilateur. Ensuite, si nous le faisons, il y aura une confusion entre ce qui provient d'une interface et ce qui ne l'est pas, car tous les membres de l'interface ne seront pas préfixés par implements . Et ce mot est une bouchée. Je ne suis pas prêt à écrire quelque chose comme ça :

class C extends React.Component {
    implements componentWillMount() { }
    implements componentDidMount() { }
    implements componentWillReceiveProps(props) { }
    /// ... and the list goes on
}

J'ai proposé de préfixer avec le nom de l'interface plus tôt mais @olmobrutall m'a montré que c'était une pire idée.

Quoi qu'il en soit, je suis assez convaincu que nous aurons besoin de 3 nouveaux mots-clés différents pour aborder correctement ce problème.

Je ne pense pas non plus qu'il soit nécessaire de taper implicitement les overrides puisque le compilateur nous empêchera déjà d'écrire quelque chose qui n'est pas compatible, surtout parce que c'est apparemment difficile à faire correctement.

new , override et implement ne sont-ils pas trop de surcharge de mots clés pour essentiellement la même sémantique JS ? Même si en C # ce sont des choses différentes, il n'y a virtuellement aucune différence dans JS/TS.

C'est aussi assez ambigu :

  • Pourquoi les champs sont new pour les classes mais implements pour les interfaces ?
  • Si vous modifiez une méthode à partir d'une classe de base qui a été implémentée avec une interface, que devez-vous utiliser ? Je vois quatre possibilités : override , implements , l'un des deux ou les deux en même temps ?

Pensez aussi à ceci :

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

class House extends Habitat {
    owner = new Human();
}

var house = new House();
house.owner = new Dog(); //Should this be allowed??  

La question est:

Est-ce que owner = new Human(); redéfinit le champ avec un nouveau type (mais compatible) ou attribue simplement une valeur.

Je pense qu'en général vous avez redéfini le champ sauf si vous utilisez le _magic keyword_. Ma préférence est pour this. en ce moment.

class House extends Habitat {
    this.owner = new Human(); //just setting a value, the type is still Animal
}

@olmobrutall Cela pourrait en effet être un peu trop lourd pour les mots clés, mais les 3 sont en effet des choses _différentes_.

  • override s'appliquerait aux méthodes (définies sur le prototype), ce qui signifie qu'il n'efface _pas_ la méthode d'origine, qui est toujours accessible derrière super .
  • new s'appliquerait aux champs, et signifierait que le champ d'origine a été effacé par le nouveau.
  • implements s'appliquerait à n'importe quoi d'une interface, et signifierait qu'il n'efface ni ne remplace rien qui existait déjà dans une classe parent, car une interface "n'existe pas".

Si vous modifiez une méthode à partir d'une classe de base qui a été implémentée avec une interface, que devez-vous utiliser ? Je vois quatre possibilités : override , implements , l'un des deux ou les deux en même temps ?

Il me semble assez logique que ce soit override puisque c'est effectivement ce que vous faites ici. implements signifierait que vous implémentez quelque chose à partir de l'interface qui n'existe pas autrement. Dans un sens, override et new auraient priorité sur implements .

Et à propos de votre exemple de remplacement de champ, rien ne devrait changer sur la façon dont cela fonctionne aujourd'hui. Je ne sais pas ce qui se passe ici, mais quoi que ce soit, cela ne devrait pas changer (ou peut-être devrait-il, mais ce n'est pas ce dont nous discutons ici).

Je pense que override conviendrait à la fois aux méthodes de base et aux propriétés, y compris celles abstraites (expliquées ci-dessous).

new est une idée intéressante dans son concept, mais ce mot-clé particulier peut être confondu avec l'instanciation de classe, il peut donc donner la fausse impression qu'il ne fonctionnerait que pour les classes, malgré le fait que les propriétés peuvent avoir primitive, interface, union ou même des types de fonctions. Peut-être qu'un mot-clé différent comme reassign pourrait mieux fonctionner là-bas, mais il a le problème qu'il peut donner une fausse idée dans le cas où la valeur est seulement re-déclarée mais pas réellement affectée à quoi que ce soit dans la classe dérivée ( new peut aussi avoir ce problème). Je pense que redefine est également intéressant, mais pourrait conduire à l'attente erronée que la propriété 'redéfinie' pourrait également avoir un type différent de celui de base, donc je ne suis pas sûr .. (_edit: en fait j'ai vérifié et il pourrait avoir un type différent tant que le nouveau est un sous-type de celui de base, donc ce n'est peut-être pas si mal_).

implement (je préfère cette forme verbale particulière pour la cohérence avec override ) semble fonctionner pour les interfaces. Je pense que cela pourrait également fonctionner techniquement pour les méthodes de base abstraites, mais l'utilisation override pourrait sembler plus cohérente, malgré la sémantique légèrement différente. Une autre raison est qu'il serait un peu gênant de changer une méthode d'abstrait en non abstrait, car il faudrait aller dans toutes les classes dérivées et changer override en implement .

Il pourrait y avoir de meilleures idées, mais c'est tout ce que j'ai à ce stade.

J'ai proposé le mot-clé new car c'est celui que C# utilise pour cette fonctionnalité exacte, mais je reconnais que ce n'est pas le meilleur. implement est en effet un meilleur choix implements . Je ne pense pas que nous devrions l'utiliser pour les méthodes abstraites car elles font partie d'une classe de base et non d'une interface. override + new -> classe de base, implement -> interface. Cela semble plus clair.

@JabX

À propos du remplacement du prototype par le champ d'instance

J'ai vérifié et tu as raison :

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

Échoue avec l'erreur du compilateur : Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Je ne vois pas vraiment la raison d'échouer dans ce cas, puisque le contrat de type parent est rempli et vous pouvez quand même écrire ceci :

var p = new SuperMan();
p.walk = () => { };

Ou même

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { };
    }
}

Environ override

override s'appliquerait aux méthodes (définies sur le prototype), ce qui signifie qu'il n'efface pas la méthode d'origine, qui est toujours accessible derrière super.

C'est en fait un argument. Il y a au moins une différence observable entre override et implements ... mais pas tout à fait.

override et implements sont implémentés dans le prototype , pouvoir utiliser super est indépendant, car vous appelez super.walk() pas seulement super() , et est disponible dans toutes les méthodes (overrides, implements, news et définitions normales).

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { } //goes to prototype
     new walk = () => { }

     implements fly() {/*...*/}  //also goes to the prototype
     implements altitude = 1000;
}

Et pouvoir utiliser super.walk() fonctionne également si vous attribuez à l'instance

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { super.walk(); };
    }

    //or with the this. syntax
    this.walk = () => { super.walk(); };
}

Il existe déjà un moyen clair de différencier les champs prototype des champs d'instance et c'est le jeton = .

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date(); //instance
     this.talk() { } //prototype
     this.walk = () => { } //instance

     this.fly() {/*...*/}  //prototype
     this.altitude = 1000; //instance
}

Avec la syntaxe this. , le problème est résolu de manière plus élégante :

  • this. signifie vérifier mon type (classes de base et interfaces implémentées).
  • = signifie attribuer à l'instance au lieu du prototype.

Environ New

new s'appliquerait aux champs, et signifierait que le champ d'origine a été effacé par le nouveau.

Tenez compte du fait que vous ne pouvez pas modifier le champ ou la méthode avec un type différent car vous casserez le système de type. En C# ou Java, lorsque vous faites new , la nouvelle méthode ne sera utilisée que sur les appels distribués statiquement au nouveau type. En Javascript, tous les appels seront dynamiques et si vous modifiez le type, le code se cassera probablement au moment de l'exécution (contraindre le type pourrait cependant être autorisé à des fins pratiques, mais pas d'élargissement).

class Person {
    walk() { }

    run() {
        this.walk();
        this.walk();
        this.walk();
    }
}

class SuperMan extends Person {
    new walk(destination: string) { } //Even if you write `new` this code will break
}

Donc plus de new le mot-clé devrait être assign , car c'est la seule chose sûre que vous puissiez faire :

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     assign dateOfBirth = new Date();
}

Notez que reassign n'a pas de sens, car Person déclare uniquement le champ mais ne lui attribue aucune valeur.

Écrire assign semble redondant, cependant, il est clair que nous attribuons parce que nous avons un = de l'autre côté.

Encore une fois, la syntaxe this. résout le problème avec élégance :

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();
}

Je ne sais pas comment vous appliquez cela aux champs d'une manière qui a du sens.

Nous parlons d'ajouter un mot-clé qui n'est applicable que dans le corps de la classe, mais les champs peuvent être réaffectés à _n'importe quel moment_ pendant la durée de vie de l'instance.

Typescript permet déjà d'initialiser des champs dans le corps de la classe :

class Person {
    dateOfBirth : new Date();
}

Il permet également de réinitialiser les champs déclarés dans la classe parent, mais la situation est mauvaise. Bien sûr, vous pouvez mal écrire le nom (problème similaire à override ) mais le type est également effacé. Cela signifie que:

class Person {
    fullName: { firstName: string; lastName?: string };
}

class SuperMan extends Person {
    fullName = { firstName: "Clark" };

    bla() {
        this.fullName.lastName; //Error
    }
}

Ce code échoue avec : La propriété 'lastName' n'existe pas sur le type '{ firstName : string ;

Nous parlons d'ajouter un mot-clé qui n'est applicable que dans le corps de la classe, mais les champs peuvent être réaffectés à tout moment pendant la durée de vie de l'instance.

Aussi les méthodes :

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

    changeMe(){
        this.talk = () => { };      
    }

    changeMyPrototype() {
        SuperMan.prototype.talk = () => { };
    }
}

@kungfusheep Je suis d'accord, les champs peuvent être réaffectés, mais les méthodes sur le prototype aussi.
La principale raison pour laquelle nous aurions besoin d'un mot-clé pour les champs "substituants" est que les méthodes d'instance _sont_ des champs et qu'elles sont largement utilisées à la place des méthodes prototypes appropriées car elles sont lambdas et se lient donc automatiquement au contexte.

@olmobrutall

À propos du remplacement des méthodes avec des champs

C'est interdit et avec raison je crois (ce n'est pas la même chose donc ça ne devrait pas être compatible), mais tu as raison c'est possible de le faire en manipulant directement l'instance de la classe. Je ne sais pas exactement pourquoi c'est autorisé, mais nous ne sommes pas ici pour changer le fonctionnement de la langue, donc je pense que nous ne devrions pas interférer ici.

À propos des interfaces

Les interfaces de classe décrivent le côté instance d'une classe, c'est-à-dire les champs ainsi que les méthodes du prototype. Je ne pense même pas qu'il y ait une différence entre les méthodes d'instance et de prototype dans une interface, je pense que vous pouvez décrire une méthode comme l'une ou l'autre et l'implémenter comme l'une ou l'autre. Ainsi, implement pourrait (et devrait) également s'appliquer aux champs.

À propos de nouveau

new devrait avoir la même sémantique que override , donc évidemment vous ne pouvez pas changer le type en quelque chose qui n'est pas compatible. Je veux dire, encore une fois, nous ne changeons pas la façon dont la langue fonctionne. Je voulais juste un mot-clé distinct car ce n'est pas le même type de redéfinition. Mais, je suis d'accord que, comme vous me l'avez montré, nous pouvons déjà distinguer les champs et les méthodes en utilisant = , donc peut-être que le même mot clé/syntaxe pourrait être utilisé sans ambiguïté.

À propos de ça.

Cependant, la syntaxe unifiée de remplacement de champ/méthode ne devrait pas être this.method() ou this.field car elle ressemble beaucoup à quelque chose qui pourrait être du Javascript valide, alors que ce n'est pas le cas. L'ajout d'un mot-clé avant le membre serait plus clair sur le fait qu'il s'agit bien d'une chose Typescript.
this est une bonne idée cependant, alors peut-être pourrions-nous laisser tomber le point et écrire quelque chose de moins ambigu comme

class Superman {
    this walk() { }
}

Mais quand même, ça a l'air un peu bizarre. Et le mélanger avec implement semblerait un peu déplacé (parce que nous sommes d'accord avec le fait que cette syntaxe this ne serait pas utilisée avec l'interface ?), Et j'aime le implement / override duo. Un modificateur override sur les champs m'irrite toujours, mais c'est peut-être mieux que d'avoir un troisième mot-clé ambigu new . Puisque l'assignation ( = ) fait clairement la différence entre une méthode et un champ, cela me conviendrait maintenant (contrairement à ce que je disais il y a quelques heures).

Je suis d'accord, les champs peuvent être réaffectés, mais il en va de même pour les méthodes sur le prototype.

Ouais, modifier le prototype directement, dans TypeScript, est tout à fait dans le domaine de déconner sous le capot. C'est loin d'être aussi courant que de simplement réaffecter une propriété sur une instance de quelque chose. Nous utilisons très rarement des champs comme fonctions, sur une base de code de plus de 150 000 lignes, donc je pense que dire qu'il est largement utilisé est peut-être subjectif.

Je suis d'accord avec la plupart de vos autres points, mais je ne suis pas convaincu (et à ce qu'il paraît, vous non plus...) de l'utilisation du mot-clé new dans ce contexte.

Nous utilisons très rarement des champs comme fonctions, sur une base de code de plus de 150 000 lignes, donc je pense que dire qu'il est largement utilisé est peut-être subjectif.

Moi non plus, mais je dois recourir à un décorateur @autobind pour lier correctement this dans mes méthodes, ce qui est plus fastidieux que de simplement écrire un lambda à la place. Et tant que vous n'utilisez pas l'héritage, l'utilisation d'un champ fonctionne comme prévu. J'avais l'impression que, du moins dans le monde JS/React, la plupart des personnes utilisant les classes ES6 utilisaient les fonctions fléchées comme méthodes.

Et les méthodes d'interface peuvent être implémentées soit avec une méthode appropriée, soit avec une méthode d'instance, ce qui n'aide pas à clarifier la différence.

Je crois que les champs ont les mêmes problèmes que les méthodes en ce qui concerne les remplacements.

class A {
    firstName: string;
    get name() {
        return this.firstName;
    }
}

class B extends A {
    firstname = "Joe" // oops
}

Une solution de contournement possible consiste à attribuer le champ dans le constructeur

class B extends A {
    constructor() {
        this.firstName = "Joe"; // can't go wrong
    }
}

mais cela ne s'applique pas aux interfaces. C'est pourquoi je (et d'autres ici) pense que nous avons besoin de quelque chose pour vérifier la préexistence d'un champ lors de sa déclaration directement dans le corps de la classe. Et ce n'est pas un override . Maintenant que j'ai mis quelques réflexions supplémentaires ici, je pense que le problème avec les champs est exactement le même que le problème avec les membres de l'interface. Je dirais que nous avons besoin d'un mot-clé override pour les méthodes qui ont déjà une implémentation dans une classe parent (méthodes prototypes, et peut-être aussi des méthodes d'instance), et un autre pour les choses qui sont définies mais sans implémentation (non-fonction champs inclus).

Ma nouvelle proposition serait, et en utilisant un mot-clé provisoire member (probablement pas le meilleur choix):

interface IBase {
    interfaceField?: string;
    interfaceMethod(): void
}

abstract class Base {
    baseField: number;
    baseMethod() { }
    baseLambda: () => { };
    abstract baseAbstractMethod();
}

class Derived extends Base implements IBase {
    member interfaceField = "Hello";
    member interfaceMethod() { }
    member baseField = 2;
    override baseMethod() { }
    override baseLambda = () => { };
    member baseAbstractMethod() { }
}

Notez que je choisirais le mot-clé member pour une méthode abstraite puisque j'ai dit que override serait pour les choses avec une implémentation, indiquant que nous remplaçons un comportement existant. Les méthodes d'instance devraient utiliser override car il s'agit d'un type de champ spécial (qui est déjà reconnu comme tel par le compilateur puisque les signatures de méthode peuvent être implémentées avec lui).

Je pense que Typescript devrait être une version de JavaScript vérifiée au moment de la compilation, et en apprenant TS, vous devriez également apprendre JS, tout comme en apprenant C #, vous obtenez une bonne intuition du CLR.

L'héritage prototypique est un concept cool de JS, et assez central, et TS ne devrait pas essayer de le cacher avec des concepts d'autres langages qui n'ont pas trop de sens ici.

L'héritage prototype ne fait aucune différence si vous héritez de méthodes ou de champs.

Par exemple:

class Rectangle {
       x: number;
       y: number;
       color: string;
}

Rectangle.prototype.color = "black";

Ici, nous définissons un champ simple dans l'objet prototype, de sorte que tous les rectangles seront noirs par défaut sans avoir à avoir un champ d'instance pour cela.

class BoundingBox {
      override color = "transparent"; // or should be member? 
}

De plus, le mot-clé member rend jaloux les autres membres de la classe.

Ce dont nous avons besoin, c'est d'une syntaxe qui autorisera dans un contexte de déclaration de classe le même type de comportement (vérification au moment de la compilation/auto-complétion/renommage) que nous avons déjà dans les expressions littérales d'objet ou membres.

Peut-être qu'une meilleure alternative à this. est juste . :

class Derived {
   .interfaceField = "hello";
   .interfaceMethod() {}
   .baseField = 2;
   .baseMethod() {}
   .baseLambda = () => {};
   .baseAbstractMethod(){};

   someNewMethod(){}
   someNewField = 3;
}

En conclusion, je ne pense pas que le Typesystem devrait suivre quelles valeurs proviennent du prototype et lesquelles de l'instance, car c'est un problème difficile une fois que vous pouvez accéder au prototype impérativement, et parce que cela ne résoudra aucun problème alors que limiter l'expressivité de JS.

Il n'y a donc aucune différence entre un champ de fonction, un champ de fonction fléché et une méthode, ni dans le système de type, ni dans le code généré (si this n'est pas utilisé).

Hé les gars, c'est un truc intéressant, mais c'est plutôt sur une tangente en termes de override spécifiquement. Je serais heureux d'avoir un nouveau sujet de discussion pour ce genre de chose, à moins que nous puissions relier ces commentaires à la suggestion d'origine plus concrètement.

Une grande partie de ce que vous dites ici n'est vraiment pas spécifique à TypeScript non plus, donc démarrer un fil ESDiscuss peut également être approprié. Certes, ils ont tout autant pensé à ce genre de chose (en termes de ce qui se passe sur le prototype par rapport à l'instance).

@olmobrutall
Les classes ES6 cachent déjà les prototypes, et comme @kungfusheep l' a dit, jouer directement avec n'est pas exactement une chose normale à faire.

Il n'y a donc aucune différence entre un champ de fonction, un champ de fonction fléché et une méthode, ni dans le système de type, ni dans le code généré (si celui-ci n'est pas utilisé).

Eh bien, dans le code généré, les méthodes de classe sont placées dans le prototype et tout le reste (non préfixé par static ) est placé dans l'instance, ce qui fait une différence avec l'héritage.

Quoi qu'il en soit, je suis maintenant d'accord sur le fait que nous n'avons pas besoin d'avoir une syntaxe distincte pour les deux types de méthodes, mais si nous devions utiliser le mot-clé override , il devrait être limité aux méthodes et nous devrions trouver autre chose pour le reste. Avoir un mot-clé unique pour tout pourrait être agréable, mais il devrait être très clair sur sa signification. override est clair, mais uniquement pour les méthodes. Votre syntaxe par points, comme this. est encore trop proche du JS existant à mon goût.

@RyanCavanaugh

C'est assez hors de la tangente en termes de override

Mes problèmes avec le remplacement sont que ce n'est pas assez général pour les méthodes et les champs d'interface. Je vois trois options :

  • utilisez override uniquement pour les méthodes de la classe de base.
  • utilisez également override pour les méthodes d'interface et les champs
  • envisager des syntaxes alternatives ( member , implement , this. , . , etc...)
  • ne rien faire (ce sera triste)

Je pense que cette décision devrait être prise ici.

Beaucoup de ce que vous dites n'est vraiment pas spécifique à Typescript

Absolument! nous sommes tous satisfaits de l'état actuel du transpilage, mais pas de la vérification de type / de l'outillage.

Quel que soit le mot-clé, ce sera Typescript uniquement (tout aussi abstrait)

@JabX

Vous avez raison sur le code généré, bien sûr. Mon point était que vous pouvez écrire quelque chose comme ceci:

class Person {
    name: string = "John"; 

    saySomething() {
        return "Hi " + this.name;
    }
}

Nous déclarons une classe, name ira à l'instance tandis que saySomething au prototype. Je peux quand même écrire ceci :

Person.prototype.name = "Unknown"; 

Parce que le type de Person.prototype est la personne entière. Typescript ne garde pas une trace de ce qui se passe où sur son système de type pour plus de simplicité.

Avoir un mot-clé unique pour tout pourrait être agréable, mais il devrait être très clair sur sa signification.

Ce que je pense est plus important et nous pourrions tous être d'accord, c'est la sémantique :

XXX modifié vérifie que le membre est déjà déclaré dans la classe de base ou les interfaces implémentées, généralement utilisées pour remplacer les fonctions de la classe de base.

Étant donné que . ou this. semblent trop étrangers et que member est redondant, je pense que le meilleur choix est probablement d' abuser override pour tous les cas . Définir des champs ou implémenter des méthodes d'interface est de toute façon une fonctionnalité secondaire.

Il existe de nombreux précédents :

  • En C#, un static class n'est plus vraiment une _classe d'objets_.
  • Il n'y a rien de _statique_ dans les champs static .
  • Les méthodes virtual sont assez _concrètes_ (VB utilise Overrideable ).

Il ressemblera à ceci:

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     override dateOfBirth = new Date();

     override talk(){/*...*/}
     override walk = () => {/* force 'this' to be captured*/} 

     override  fly() {/*...*/}
     override altitude = 1000; 
}

Pouvons-nous nous contenter de cela ?

Je préférerais voir un mot-clé implement séparé pour tout ce qui vient des interfaces (avec priorité à override si c'est dans les deux), car c'est ce que c'est : une implémentation, pas un remplacement.
Sinon, je suis d'accord qu'il serait peut-être préférable d'abuser override pour tout de la classe parent.

Mais il n'y a pas de différence sémantique entre implement et override .

Les deux vont avoir des messages d'auto-complétion/d'erreur/de transpilation similaires à JS... c'est juste une différence philosophique.

Cela vaut vraiment la peine d'expliquer deux mots-clés et qu'un compilateur pédant vous dit : Error you should use 'override' instead of 'implement' .

Qu'en est-il de ce cas :

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

La question est... qui s'en soucie ?.

Il y a aussi le problème implement / implements .

Abusons simplement de override . Un concept un mot-clé, c'est l'important.

Eh bien, j'ai déjà proposé que override ait la priorité sur implement , mais je n'ai probablement pas été assez clair.
Je ne crois toujours pas que ce soit la même chose. Autre exemple : pour un membre d'interface obligatoire, un échec d'implémentation est une erreur du compilateur alors qu'un échec de remplacement n'est pas un problème (sauf si le membre de base est abstrait, c'est-à-dire...).

Je ne pense tout simplement pas que override sur quelque chose qui n'est pas déclaré sur une classe parent aurait un sens. Mais je suis peut-être le seul à vouloir la distinction. Dans tous les cas, quel que soit le mot-clé que nous finirons par utiliser pour cela, j'espère simplement que nous pourrons nous installer sur quelque chose et fournir un mode strict pour imposer son utilisation.

Eh bien, j'ai déjà proposé que le remplacement ait la priorité sur l'implémentation, mais je n'ai probablement pas été assez clair.

Bien sûr, je voulais juste dire qu'il y a quatre cas. Classe de base, interface, interface dans la classe de base, réimplémentation de l'interface dans la classe de base.

@JabX

Je ne pense tout simplement pas que le remplacement de quelque chose qui n'est pas déclaré sur une classe parente aurait un sens. Mais je suis peut-être le seul à vouloir la distinction .

Vous n'êtes pas. Ce sont deux choses fondamentalement différentes et il y a avantage à avoir cette séparation au niveau linguistique.

Je pense que si les interfaces doivent être prises en charge dans cette nouvelle fonctionnalité, cela ne peut pas être une solution à moitié cuite qui a été boulonnée sur override . Pour la même raison que extends existe en tant qu'entité distincte de implements au niveau de la classe, override doit être associé à quelque chose comme implement dans l'ordre pour résoudre ce problème. C'est _remplacer la fonctionnalité_ vs _définir la fonctionnalité_.

@olmobrutall

Cela vaut vraiment la peine d'expliquer deux mots clés et qu'un compilateur pédant vous dit : Erreur, vous devriez utiliser 'override' au lieu de 'implémenter'.

J'ai l'impression d'avoir fait cette distinction environ 4 fois maintenant, mais ce n'est pas pédant ; c'est une information vraiment importante !

Considérez votre propre exemple

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

"ou met en œuvre ..." est une hypothèse totalement incorrecte à faire dans ce cas. Ce n'est pas parce que vous avez dit au compilateur que vous voulez implémenter la même interface que la classe de base que vous étendez que vous avez déjà implémenté compare .
Si vous deviez écrire implement dans ChildClass au lieu de override , vous devriez être reconnaissant que le compilateur vous dise alors votre hypothèse incorrecte, car c'est un gros problème que vous étiez sur le point d'effacer sans le savoir une méthode déjà implémentée !

Dans les bases de code dont je suis responsable, ce serait sans aucun doute un gros problème ; Je salue donc toute fonctionnalité du compilateur qui peut empêcher de telles erreurs de développement !

@kungfusheep

Si vous deviez écrire implémenter dans ChildClass au lieu de remplacer, vous devriez être reconnaissant que le compilateur vous informe alors de votre hypothèse incorrecte, car c'est un gros problème que vous étiez sur le point d'effacer sans le savoir une méthode précédemment implémentée !

Si le remplacement d'une méthode déjà implémentée est une décision si importante, alors implement doit également être utilisé pour les méthodes abstraites. Si vous modifiez une méthode de abstract à virtual ou l'inverse, vous devez vérifier (et modifier) ​​toutes les versions implémentées pour envisager d'appeler super ou simplement de supprimer la méthode.

En pratique, cela n'a jamais été un problème en C#.

Je suis d'accord, override pour les méthodes implémentées, et implements pour les méthodes non implémentées (abstrait/interface).

Il pourrait être utilisé pour le résumé, oui.

En C #, il n'y avait pas le scénario d'interface "facultatif", donc peut-être n'était-il pas considéré comme un problème.

Mais ce que je veux dire, c'est qu'en C#, nous override avons implémenté et non implémenté des méthodes et je n'ai jamais entendu quelqu'un se plaindre.

Je ne pense pas que ce soit vraiment un problème qui mérite d'expliquer la fonctionnalité au moins deux fois plus difficile.

Eh bien, la seule raison pour laquelle nous avons besoin d'un mot-clé implement est que les membres de l'interface peuvent être facultatifs, alors que ce n'est pas possible en C# (et probablement aussi dans la plupart des langages OO). Il n'y a pas de mot-clé pour cela car vous ne pouvez pas implémenter complètement une interface, donc pas de problème. Ne basez pas trop votre argumentation sur "c'est la même chose en C#", car nous avons ici des problématiques différentes.

override est utilisé pour les méthodes _base class_, qu'elles soient abstraites ou non. Je suppose que nous devrions faire la même chose ( override au lieu de implement pour les méthodes abstraites) ici parce que je pense que la distinction devrait porter sur l'origine de la méthode (classe ou interface) plutôt que sur l'existence de la mise en œuvre. Et à l'avenir, si vous décidez de donner une implémentation par défaut à votre méthode abstraite, vous n'aurez pas à parcourir votre code (ou pire, le code des autres utilisant votre classe) pour remplacer les mots-clés.

Ma question principale est maintenant, devrions-nous avoir un indicateur strict override / implement (je le veux totalement), devrions-nous forcer le mot-clé implement sur les membres d'interface obligatoires ? Parce que cela n'aide pas beaucoup (vous ne pouvez pas manquer de les implémenter car sinon il ne compilera pas) et pourrait conduire à beaucoup de verbosité inutile. Mais d'un autre côté, il peut être trompeur d'avoir le implement sur certains membres de l'interface mais pas sur tous.

@JabX Je voudrais personnellement un avertissement si une classe de base ajoutait une implémentation d'une méthode abstraite.

Cependant, je ne suis pas convaincu à 100% par l'idée d'un mot-clé d'outils en premier lieu. Le compilateur vous avertira déjà si vous n'avez pas implémenté quelque chose. Le seul endroit où il est vraiment utile est pour les méthodes facultatives.

Et de toute façon, cela n'a rien à voir avec la raison pour laquelle je suis dans ce fil. Je cherchais juste s'il y avait un moyen de spécifier une fonction comme remplacement d'une classe parent.

Ma question principale est maintenant, devrions-nous avoir un indicateur de remplacement/implémentation strict (je le veux totalement), devrions-nous forcer le mot-clé implement sur les membres d'interface obligatoires ?

Je pense que ne pas l'écrire devrait être un avertissement.

Le seul endroit où il est vraiment utile est pour les méthodes facultatives.

Ouais, c'est un peu tout le problème ici.

Sans cela, il s'agit d'une erreur très courante dans la méthode de faute de frappe que vous souhaitez remplacer, mais en fait, vous créez une nouvelle méthode. L'introduction d'un tel mot-clé n'est qu'un moyen de souligner l'intention de remplacer la fonction.

Cela pourrait être un avertissement ou quoi que ce soit, mais actuellement c'est très douloureux.

J'ajoute une annotation de remplacement à la vue de classe UML dans alm.tools :rose:

image

réfs https://github.com/alm-tools/alm/issues/84

J'ai également ajouté un indicateur de gouttière pour les membres de classe qui remplacent un membre de classe de base dans alm .

overrides

réfs https://github.com/alm-tools/alm/issues/111

Accepter les PR pour une solution étendue, selon nous, apporte le plus de valeur avec le moins de complexité :

  • Le nouveau mot-clé override est valide sur les méthodes de classe et les déclarations de propriété (y compris get/set)

    • Toutes les signatures (y compris la mise en œuvre) doivent avoir override si l'on en a

    • get et set doivent être marqués override si l'un ou l'autre est

  • C'est une erreur d'avoir override s'il n'y a pas de propriété/méthode de classe de base avec ce nom
  • Le nouveau commutateur de ligne de commande --noImplicitOverride (n'hésitez pas à changer le nom ici) rend override obligatoire pour les choses qui sont des remplacements

    • Ce commutateur n'a aucun effet dans les contextes ambiants (c'est-à-dire les fichiers declare class ou .d.ts)

Hors champ pour le moment :

  • Héritage des signatures
  • Typage contextuel des paramètres ou des initialiseurs (essayé plus tôt et ce fut un désastre)
  • Mot-clé correspondant pour indiquer "J'implémente un membre d'interface avec cette déclaration"

@RyanCavanaugh
J'essaie actuellement de l'implémenter (je viens de commencer, et j'apprécierais toute aide à ce sujet, en particulier pour les tests), mais je ne suis pas sûr de comprendre ce qu'il y a derrière

Toutes les signatures (y compris la mise en œuvre) doivent avoir override si on le fait

Puisque vous ne pouvez pas avoir plusieurs signatures d'une méthode dans une classe, parlez-vous du contexte d'héritage ? Voulez-vous dire que (sans le drapeau qui impose l'utilisation de override , sinon c'est évidemment une erreur)

class A {
    method() {}
}

class B extends A {
    override method() {}
}

class C extends B {
    method() {} 
}

devrait afficher une erreur dans la classe C car je n'ai pas écrit override avant method ?

Si tel est le cas, je ne sais pas pourquoi nous voudrions appliquer cela. Et est-ce que ça devrait aussi marcher dans l'autre sens ? Afficher une erreur dans la classe B si j'ai spécifié le override en C et non en B ?

Oui, je pense que cela s'applique aux signatures d'arbre d'héritage.
Et je ne pense pas que cela fonctionnera dans l'autre sens, il devrait commencer à appliquer la règle de remplacement une fois qu'elle est utilisée à un niveau de la hiérarchie. Dans certains cas, la décision d'appliquer un remplacement peut être prise sur une classe dérivée d'une classe n'appartenant pas au développeur. Ainsi, une fois utilisé, il ne devrait s'appliquer qu'à toutes les classes dérivées.

j'ai une autre question sur

get et set doivent être marqués override si l'un ou l'autre est

Que se passe-t-il si ma classe parent ne définit qu'un getter et que je veux définir dans ma classe dérivée le setter ? Si je suis cette règle, cela signifie que mon setter devrait être défini comme un remplacement, mais il n'y a pas de setter dans la classe parent, et ce devrait être une erreur de remplacer quelque chose qui n'existe pas. Eh bien, en pratique (dans mon implémentation naïve actuelle), il n'y a pas de contradiction puisque le getter et le setter sont pour la même propriété.
Je pourrais même écrire quelque chose comme :

class A {
    get value() { return 1; }
}

class B extends 1 {
   override set value(v: number) {}
}

Ce qui me semble carrément faux.

Puisque vous ne pouvez pas avoir plusieurs signatures d'une méthode dans une classe

Vous pouvez avoir plusieurs signatures de surcharge pour la même méthode :

class Base { bar(): { } }

class Foo extends Base {
  // Must write 'override' on each signature
  override bar(s: string): void;
  override bar(s?: number): void;
  override bar(s: string|number) { }
}

Je pense que votre intuition concernant les autres cas est correcte.

En ce qui concerne les getters et les setters, je pense que override s'applique uniquement à l'emplacement de propriété. Écrire un setter prioritaire sans getter correspondant est clairement extrêmement suspect, mais ce n'est ni plus ni moins suspect en présence de override . Comme nous ne savons pas toujours comment la classe de base a été implémentée, je pense que la règle est simplement qu'un getter ou setter override doit avoir _some_ propriété de classe de base correspondante, et que si vous dites override get { , toute déclaration set doit aussi avoir override (et vice versa)

Oh ok, je ne savais pas qu'on pouvait écrire des signatures de méthode dans des classes comme ça, c'est plutôt chouette.

Je pense que virtual aurait du sens aussi.
Tous les mots clés : virtual , override , final feraient en fait une grande différence, en particulier lors de la refactorisation des classes.
J'utilise beaucoup l'héritage, il est maintenant si facile de remplacer la méthode "par erreur".
virtual améliorerait également intellisense, car vous ne pourriez proposer que des méthodes abstraites/virtuelles après le mot-clé override.

S'il vous plaît, s'il vous plaît, accordez-leur la priorité. Merci!

Je suis d'accord avec @pankleks - j'utilise beaucoup l'héritage, et je me sens mal de remplacer une fonction sans rien spécifier directement.

"virtuel", "override" et "final" seraient parfaits.

C'est une question importante pour moi.

@RyanCavanaugh

Typage contextuel des paramètres ou des initialiseurs (essayé plus tôt et ce fut un désastre)

Pouvez-vous élaborer sur ce sujet? En fait, j'ai trouvé ce problème en essayant de savoir pourquoi TS ne déduit pas le type de mes paramètres sur les remplacements de méthode, ce qui m'a surpris. Je ne vois pas quand une liste de paramètres non identiques serait correcte lors du remplacement, ou un type de retour qui n'est pas compatible.

@pankleks

Je pense que virtual aurait du sens aussi.

Par nature, tout est "virtuel" dans JS. La prise en charge de final suffirait à mon humble avis : si vous avez une méthode (ou une propriété) qui ne doit pas être remplacée, vous pouvez la marquer comme telle pour déclencher une erreur du compilateur en cas de violation. Pas besoin de virtual alors, n'est-ce pas ? Mais cela est suivi dans le numéro 1534.

@avonwyss il y a en quelque sorte deux problèmes ici.

Lorsque nous avons essayé de saisir contextuellement _property initializers_, nous avons rencontré certains problèmes décrits sur https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207 . Nous pensons avoir une nouvelle approche plus réussie au #10570

Les déclarations de méthode sont une boule de cire différente et là, nous avons d'autres choses à comprendre, comme ce qui devrait se passer ici :

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x) { // x: ???
    return x;
  }
}

Nous pouvons simplement décider que seules les méthodes à signature unique obtiennent les types de paramètres de la base. Mais cela ne va pas rendre tout le monde heureux et ne résout pas vraiment le problème fréquent de "Je veux que ma méthode de classe dérivée ait les mêmes _signatures_ mais fournisse juste une _implémentation_ différente".

Eh bien, le type x serait string | number dans ce cas, mais je comprends qu'il peut être assez difficile de le savoir de manière cohérente.

Mais vous ne voudriez probablement pas que le type _vu de l'extérieur_ de x soit string | number - en supposant que Derived ait la même sémantique que Base , ce serait ' t être légal d'invoquer avec (3) ou ('foo', 3)

Hum ouais, j'ai raté ça.

Je vois que vous avez suggéré de le limiter aux méthodes à signature unique, mais je pense qu'il pourrait être "facilement" étendu aux méthodes de "signature correspondante", comme dans votre exemple:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x, count) { // x: number, count: number
    return [];
  }
}

car il n'y a qu'une signature sur la classe de base qui correspond.

Ce serait bien mieux que ce que nous avons (rien), et je ne pense pas que ce serait si difficile à faire ? (toujours hors de portée pour ce problème et probablement plus de travail)

@RyanCavanaugh Merci pour la réponse. Comme indiqué, mon espoir pour override est qu'il réglerait le problème de paramètre (et de type de retour). Mais je ne vois pas votre exemple comme problématique, car les surcharges doivent toujours avoir une seule implémentation "privée" compatible avec toutes les surcharges (c'est pourquoi la suggestion de @JabX ne fonctionnerait pas conceptuellement).
Donc, nous aurions quelque chose comme ça :

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
  // implies: method(x: string|number, count?: number): string[]|number[]
  // or fancier: method<T extends string|number>(x: T, count?: number): T[]
}
class Derived extends Base {
  override method(x, count?) { // may only be called like the method on Base
    return [x];
  }
}

Cette logique existe déjà et le remplacement ne serait évidemment disponible que pour la méthode d'implémentation "privée", pas les remplacements distincts (tout comme vous ne pouvez pas implémenter explicitement une seule surcharge de toute façon). Donc, je ne vois pas de problème ici - ou ai-je raté quelque chose?

Les méthodes conçues pour être remplacées n'ont généralement pas de surcharges de toute façon, donc même prendre la solution simple et ne pas déduire le paramètre et les types de retour si des surcharges existent serait parfaitement bien et utile. Ainsi, même si le comportement était tel quel lors du remplacement des méthodes surchargées, vous obtiendriez une erreur lors de l'exécution en mode --no-implicit-any et vous devriez spécifier des types compatibles, cela semblerait être une approche parfaitement correcte.

Il me manque probablement quelque chose, mais en Javascript (et donc Typescript), vous ne pouvez pas avoir 2 méthodes portant le même nom - même si elles ont des signatures différentes ?

En C#, vous pourriez avoir

public string method(string blah) {};
public int method(int blah) {};

Mais ici, vous ne pouvez jamais avoir 2 fonctions avec le même nom, peu importe ce que vous faites avec les signatures... donc aucun de vos exemples ne serait de toute façon possible, n'est-ce pas ? À moins que nous ne parlions de plusieurs extensions sur la même classe... mais ce n'est pas ce que montrent vos exemples, alors probablement pas ? Est-ce un non-problème ?

En ce moment, j'utilise l'héritage, et c'est très frustrant... Je dois mettre des commentaires sur les fonctions pour indiquer qu'elles sont virtuelles (c'est-à-dire que je les remplace quelque part) ou qu'elles sont remplacées (c'est-à-dire que cette fonction remplace une fonction sous-jacente). Super ennuyeux et salissant! Et dangereux !

@ sam-s4s Oui, il est possible de déclarer plusieurs signatures de surcharge logique pour une méthode, mais elles se retrouvent toutes sur la même implémentation unique (qui doit ensuite déterminer par les paramètres passés quelle "surcharge" est appelée). Ceci est largement utilisé par de nombreux frameworks JS, par exemple jQuery où $(function) ajoute une fonction prête, mais $(string) recherche le DOM par sélecteur.

Voir ici pour les docs : https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ah oui, des déclarations, mais pas des implémentations, ça a du sens :) Merci

Cela a duré trop longtemps, et je trouve ennuyeux de devoir lire tout ce fil gonflé pour même commencer à comprendre pourquoi TS n'a toujours pas cette assertion de base au moment de la compilation.

Où en sommes-nous soit (a) un mot-clé override ou (b) une annotation jsdoc @override qui informe le compilateur "une méthode ayant le même nom et la même signature de type DOIT exister sur une superclasse". ?

Plus précisément, le commentaire dans https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8 juin 2016) par @RyanCavanaugh suggère-t-il que la prochaine étape est une RP (communautaire) ?

class Bar extends Foo {
  /**
   * <strong i="12">@override</strong>
   */
  public toString(): string {
     // ... 
  }

  override public toString(): string {
    // ...
  }
}

Plus précisément, le commentaire dans #2000 (commentaire) (8 juin 2016) de @RyanCavanaugh suggère-t-il que la prochaine étape est une RP (communautaire) ?

@pcj Correct

J'ai commencé un PR à ce sujet au #13217. Toute autre personne intéressée par cette fonctionnalité est la bienvenue pour participer et/ou fournir des suggestions. Merci!

Je préférerais le même que pour java avec un décorateur

<strong i="6">@Override</strong>
public toString(): string {
   // ...
}

@ Chris2011 D'accord, cela semble familier, mais cela impliquerait en tant que décorateur que @Override serait évalué au moment de l'exécution, pas au moment de la compilation. Je prévois javadoc /** <strong i="7">@override</strong> */ dans un PR séparé une fois que le mot clé de remplacement #13217 ( public override toString() ) avancera dans l'examen.

Essayer de suivre la conversation de ce fil. Cela inclut-il les fonctions statiques ? Je ne vois aucune raison pour laquelle nous ne pourrions pas également autoriser le remplacement de fonctions statiques dans des classes dérivées avec des signatures complètement différentes. Ceci est pris en charge dans ES6. Si avait class A { } ; A.create = function (){} alors class B extends A { } ; B.create = function (x,y) { } , appeler A.create() ne va pas appeler B.create() et causer des problèmes. Pour cette raison (et pour créer la possibilité d'utiliser le même nom de fonction sur les types que les fonctions d'usine), vous devez également autoriser le remplacement de la signature des fonctions statiques de base. Cela ne casse rien et ajoute la possibilité de faire des choses intéressantes (en particulier pour les frameworks de moteur de jeu, où tout ce qui est « nouveau » est vraiment un mal s'il est utilisé tout le temps sans tirer d'un cache d'objets pour réduire les gels de GC). Étant donné que les constructeurs appelables ne sont pas pris en charge sous ES6, la création d'une convention de dénomination commune pour les méthodes sur les types est la seule autre option ; cependant, cela nécessite actuellement que la fonction statique dérivée affiche une surcharge de la signature de la fonction statique de base avec la sienne, ce qui n'est pas utile pour une fonction d'usine sur un type qui ne traite que CE type. :/ En attendant, la seule option que j'ai est de forcer les utilisateurs de mon framework à dupliquer toutes les signatures de fonctions statiques de la hiérarchie de base (telles que SomeType.create() ) sur leurs types dérivés, et ainsi de suite, ce qui devient vraiment idiot .

Voici un exemple de la "sottise" dont je parle (qui fonctionne, mais dont je ne serais pas fier dans un framework extensible):

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    static create(s: string);
    static create(n: number);
    static create(n:any) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(n: any) {
        super.init(n.toString());
    }
}

class C extends B {
    static create(s: string)
    static create(n: number)
    static create(b: boolean)
    static create(b: any) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(b: boolean);
    protected  init(b: any) {
        super.init(b ? 0 : 1);
    }
}

(https://goo.gl/G01Aku)

Cela aurait été bien plus sympa :

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    new static create(n:number) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    new protected init(n: number) {
        super.init(n.toString());
    }
}

class C extends B {
    new static create(b: boolean) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    new protected  init(b: boolean) {
        super.init(b ? 0 : 1);
    }
}

@rjamesnw vous pourriez être intéressé par "ceci" polymorphe pour les membres statiques ayant un contrat pour les fonctions d'usine est discuté comme cas d'utilisation principal tout au long des commentaires.

donc, réaffirmant à nouveau un sentiment que @pcj a exprimé ( commentaire ) il y a plus d'un an, il est déroutant de devoir lire autant de relations publiques et de commentaires ici et ailleurs pour déterminer où en est cette demande de fonctionnalité.

il semble que # 13217 était si proche, puis abattu à nouveau par @DanielRosenwasser et sa société comme une fonctionnalité qui ne correspond peut-être pas à la langue, reprenant apparemment la conversation circulaire ici sur cette question pour savoir si cela devrait être fait ou non. Peut-être que ce problème de décorateurs "au moment de la conception" (#2900) le résoudra, peut-être pas ? Ce serait bien de le savoir.

Voici quelques autres lacunes de l'approche du décorateur d'exécution publiées il y a quelques années que je n'ai pas vues mentionnées :

  • Cela ne fonctionnera pas avec les propriétés car elles ne font pas partie du prototype
  • Les propriétés et les accesseurs peuvent (en quelque sorte...) se remplacer, ce qui ne fonctionnera pas non plus
  • Nous avons maintenant des classes abstraites, mais comme les choses abstraites n'existent pas réellement au moment de l'exécution, cela ne peut pas fonctionner avec elles

Si la seule mise en garde était que les vérifications se produisaient lors de l'initialisation de la classe, je pourrais probablement l'accepter comme une mesure temporaire, mais en l'état, il y a tout simplement trop de limitations.

Franchement, je ne peux pas croire que cette fonctionnalité soit toujours aussi controversée depuis 3 ans depuis que j'ai enregistré le problème pour la première fois. Chaque langage orienté objet "sérieux" prend en charge ce mot-clé, C#, F#, C++... vous l'appelez.

Vous pouvez discuter d'hypothèses toute la journée sur les raisons pour lesquelles javascript est différent et nécessite une approche différente. Mais d'un point de vue pratique , travaillant quotidiennement dans une très grande base de code Typescript, je peux vous dire que le remplacement ferait une énorme différence pour la lisibilité et la maintenance du code. Cela éliminerait également toute une classe de bogues dus aux classes dérivées remplaçant accidentellement les méthodes de la classe de base avec des signatures compatibles mais légèrement différentes.

J'aimerais vraiment voir une implémentation correcte de virtual / override / final. Je l'utiliserais tout le temps, et cela rendrait le code beaucoup plus lisible et moins sujet aux erreurs. J'ai l'impression que c'est une caractéristique importante.

D'accord. Il est frustrant de voir à quel point des fonctionnalités relativement obscures / marginales sont ajoutées, tandis que quelque chose d'aussi ... fondamental est rejeté. Y a-t-il quelque chose que nous puissions faire pour pousser pour cela ?

Allez! S'il y a autant de commentaires et sur trois ans, pourquoi n'est-il pas déjà mis en œuvre !

Allez :joy_cat:

Soyez constructif et précis ; nous n'avons pas besoin de dizaines de commentaires S'IL VOUS PLAÎT FAITES-LE DÉJÀ

Mais les gars, soyez également précis de votre côté.

De toute évidence, la communauté est très intéressée par cette fonctionnalité, et l'équipe TS ne nous donne aucun détail sur l'avenir de celle-ci.

Personnellement, je suis entièrement d'accord avec @armandn , les versions récentes de TS apportent des fonctionnalités relativement rarement utilisées pendant que quelque chose comme ça se déroule.

Si vous ne prévoyez pas de le faire, dites-le nous. Sinon, faites-nous savoir comment la communauté peut vous aider.

Juste une chronologie ici car il y a plus de commentaires que GitHub n'est prêt à afficher au chargement :

Ce n'est pas comme si nous ne prenions pas en considération ici. C'est aussi proche que les fonctionnalités arrivent, et nous avons rejeté nos propres idées de fonctionnalités pour des raisons similaires - voir # 24423 pour un exemple récent.

Nous voulons vraiment développer la langue intentionnellement. Cela prend du temps et vous devez vous attendre à devoir être patient. En comparaison, C++ est plus ancien que beaucoup de personnes dans ce fil ; TypeScript n'est pas encore assez vieux pour être à la maison sans la surveillance d'un adulte. Une fois que nous avons ajouté quelque chose au langage, nous ne pouvons plus le retirer, donc chaque ajout doit être soigneusement pesé par rapport à ses avantages et inconvénients. Je parle par expérience que les fonctionnalités pour lesquelles les gens criaient (méthodes d'extension, je vous regarde) auraient détruit notre capacité à continuer à faire évoluer le langage (pas de types d'intersection, pas de types d'union, pas de types conditionnels) si nous les avions implémentés juste parce que il y avait beaucoup de commentaires GitHub. Je ne dis pas que override est une chose dangereuse à ajouter, juste que nous abordons toujours cela avec le maximum absolu de prudence.

Si je devais résumer les problèmes de base avec override maintenant :

  • Cela ne vous permet pas de faire quelque chose que vous ne pouvez pas faire aujourd'hui avec un décorateur (ce n'est donc "pas nouveau" en termes de "choses que vous pouvez accomplir")
  • La sémantique ne peut pas s'aligner précisément avec override dans d'autres langues (augmente la charge cognitive)
  • Il ne "s'allume" pas à 100 % sans un nouvel indicateur de ligne de commande, et ce serait un indicateur que nous voudrions probablement être sous strict mais de manière réaliste, nous ne pourrions pas le faire car il serait trop gros un changement avec rupture (diminue la valeur livrée). Et tout ce qui implique un nouvel indicateur de ligne de commande est un autre doublement de l'espace de configuration que nous devons peser sérieusement, car vous ne pouvez doubler quelque chose qu'un certain nombre de fois avant qu'il ne consomme tout votre budget mental lors de la prise de décisions futures.
  • Un désaccord interne assez fort ici sur la question de savoir si override peut s'appliquer à la mise en œuvre d'un membre d'interface (diminue la valeur perçue car les attentes ne correspondent pas à la réalité pour une certaine proportion de personnes)

L' avantage total ici est que vous pouvez a) indiquer que vous remplacez quelque chose (ce que vous pourriez faire aujourd'hui avec un commentaire), b) détecter une faute d'orthographe pour laquelle la saisie semi-automatique aurait dû vous aider de toute façon, et c) avec un nouveau drapeau, attraper les endroits où vous avez "oublié" de mettre un mot-clé et dont vous avez maintenant besoin (une tâche mécanique simple qui a peu de chances de trouver de vrais bogues). Je comprends que b) est extrêmement frustrant , mais encore une fois, nous devons respecter la barre de complexité ici.

En fin de compte, si vous pensez que les classes JS seraient énormément aidées par un mot-clé override , alors défendre une proposition TC39 pour l'ajouter au runtime de base serait un bon point de départ.

Cela ne vous permet pas de faire quelque chose que vous ne pouvez pas faire aujourd'hui avec un décorateur (ce n'est donc "pas nouveau" en termes de "choses que vous pouvez accomplir")

Peut-être que je comprends mal cela, mais il y a des choses assez importantes qu'un décorateur ne peut pas faire et que le mot-clé pourrait faire. J'en ai mentionné quelques-uns dans https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -389393397. Corrigez-moi définitivement si ces choses sont possibles, car j'aimerais utiliser l'approche du décorateur, mais les méthodes non abstraites ne sont qu'une petite partie de mon cas d'utilisation pour cette fonctionnalité.

Un autre avantage non mentionné est qu'un mot clé de remplacement rendrait beaucoup plus faisable la modification de quelque chose dans une classe de base. Changer un nom ou diviser quelque chose en deux parties, etc. est difficile sachant que toutes les classes dérivées se compileront très bien sans être mises à jour pour correspondre, mais échoueront probablement au moment de l'exécution. De plus, étant donné qu'il n'y a pas de mots-clés final/scellés/internes disponibles, à peu près n'importe quelle classe exportée peut être dérivée quelque part, ce qui rend le changement à peu près tout ce qui n'est pas privé beaucoup plus risqué qu'il ne le serait avec un remplacement disponible.

Mon impression est qu'une règle TSLint serait de loin la voie la plus fluide ici. @kungfusheep a brièvement mentionné l'idée dans https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734 , mais je ne vois aucune suite. Quelqu'un d'autre est-il au courant d'une implémentation TSLint de cette fonctionnalité ? Sinon, je commencerai probablement à en pirater un quand j'en aurai l'occasion. 😄

Ma pensée actuelle est que ce ne sera qu'un commentaire, // @override , tout comme // @ts-ignore et diverses autres directives basées sur les commentaires que j'ai vues. Personnellement, je préférerais éviter les décorateurs car (dans leur forme actuelle) ils ont une sémantique d'exécution et puisqu'ils sont toujours au stade 2 et désactivés par défaut.

Avec un peu de chance, une règle TSLint personnalisée serait une solution à 90%, ne manquant vraiment que d'esthétique, et bien sûr, elle pourrait avancer sans avoir à s'engager sur les détails du langage. Avec les règles sensibles au type, tslint-language-service et la correction automatique, il n'y a vraiment pas beaucoup de différence entre une erreur TSLint et une erreur TS intégrée du point de vue du développeur. J'espère qu'un plugin TSLint (ou plusieurs plugins concurrents) donnerait à la communauté une certaine expérience et une chance de parvenir à un accord sur la meilleure sémantique. Ensuite, il pourrait peut-être être ajouté en tant que règle TSLint de base, et cela fournirait peut-être suffisamment de clarté et de motivation pour justifier l'avantage esthétique d'un mot-clé override dans le langage de base.

Il suffit de sortir des sentiers battus ici. Nous avons ici deux scénarios différents. C# (juste à titre d'exemple) utilise virtual et override car les méthodes ne sont PAS virtuelles par défaut. En JavaScript, toutes les fonctions d'une classe sont virtual par défaut (par nature). N'est-il pas plus logique d'inverser le processus et d'avoir un modificateur de type nooverride à la place ? Bien sûr, les gens pourraient toujours le forcer, mais au moins c'est là pour aider à pousser une convention selon laquelle certaines fonctions des classes de base ne devraient pas être touchées. Encore une fois, il suffit de penser en dehors de la norme ici. ;) Cela peut aussi être moins un changement de rupture.

N'est-il pas plus logique d'inverser le processus et d'avoir un modificateur de type nooverride à la place ?

J'aime votre façon de penser, et je pense que ce que vous recherchez est définitif .

Qu'en est-il de readonly ? Je crois que remplacer une méthode dans JS signifie en fait remplacer celle du parent par celle de l'enfant lors de l'instanciation (lorsque la chaîne de prototypes est appliquée). Dans ce cas, avoir une méthode readonly pour signifier "C'est là pour que tout le monde puisse le voir, mais je ne veux pas que quelqu'un le change, que ce soit par héritage ou dynamiquement après l'instanciation" a beaucoup de sens. C'est déjà implémenté pour les membres, pourquoi ne pas le faire aussi pour les méthodes ?

Y a-t-il déjà une proposition pour cela? Sinon, cela pourrait valoir la peine d'être étudié comme alternative à l'annulation...

_edit : _ s'avère que vous pouvez remplacer un membre en lecture seule, donc tout cet argument s'effondre.

Autoriser les fonctions de classe private est peut-être une autre option ?

Edit : je pensais "au lieu de le marquer comme final" mais je devais être à moitié endormi (évidemment "final" signifie public mais ne peut pas passer outre), LOL ; ça ne fait rien.

@rjamesnw vous pouvez déjà définir les fonctions de classe comme publiques, protégées, privées.

Je ne pense pas qu'avoir uniquement "final" serait une solution. Le problème est que les gens peuvent créer accidentellement une nouvelle fonction dans une classe qui hérite d'une classe de base avec ce nom déjà utilisé, et alors vous allez casser les choses en silence sans savoir pourquoi. Cela m'est arrivé plusieurs fois, très frustrant, car vous n'obtenez souvent pas d'erreurs, et des choses étranges se produisent (ou ne se produisent pas)...

Donc, je pense vraiment que nous examinons une nouvelle entrée tsconfig.json qui forcera le compilateur à générer une erreur lorsque quelque chose est remplacé sans qu'il soit marqué comme virtuel (et celui qui le remplace étant marqué comme override ou final).

Je pense que sans override , TypeScript déçoit ses utilisateurs sur sa promesse [perçue] de sécurité au moment de la compilation et de sécurité de type.
L'argument "utiliser une commande IDE pour remplacer la méthode" s'effondre au fur et à mesure que le code évolue (comme d'autres l'ont souligné).
On dirait également que toutes les principales langues comparables ont ajouté override sous une forme ou une autre.
Pour ne pas en faire un changement de rupture, il pourrait s'agir d'une règle tslint (de la même manière qu'en Java).
Au fait, pourquoi ne pas autoriser les changements de rupture entre les principales versions de langage, par exemple 2.x -> 3.x. Nous ne voulons pas rester coincés, n'est-ce pas ?
S'il s'avère que quelque chose ne va pas avec certains détails de override , et que cela nécessite quelques changements de rupture dans 3.x, qu'il en soit ainsi. Je pense que la plupart des gens comprendront et apprécieront le compromis entre la vitesse d'évolution et la compatibilité. Vraiment sans override je ne peux pas recommander TS comme quelque chose de plus pratique que Java ou C#...

Si les gens veulent être stricts, il devrait y avoir un moyen de override obligatoire (pourrait être une règle facultative de tslint). La valeur de obligatoire override est cependant beaucoup plus faible, car le risque de remplacer accidentellement une méthode de la superclasse semble beaucoup plus faible que de ne pas remplacer accidentellement .

Vraiment, ce numéro ouvert depuis 2015 est un seau d'eau froide sur mon enthousiasme et mon évangélisation TypeScript ...

Cela ne gère pas les méthodes de remplacement des grands-parents :

/* Put this in a helper library somewhere */ function override(container, key, other1) { var baseType = Object.getPrototypeOf(container); if(typeof baseType[key] !== 'function') { throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method'); } }

Il est également triste et décevant que No one assigned sur GH ici sur cette question. Peut-être est-il temps pour un fork TypeScript ? ;) CompileTimeSafeScript...

Tout ce fil ressemble à un grand anti-modèle "Paralysie par analyse". Pourquoi ne pas simplement faire la "valeur de 80 % des cas avec 20 % de travail" et, l'essayer dans la pratique, et, si nécessaire, l'ajuster en 3.x ?

Les cas d'utilisation simples, fréquents et très précieux sont pris en otage par certains cas d'urgence dont la plupart des gens n'auront jamais vraiment besoin de s'inquiéter.

Ainsi, il est possible de vérifier le type au moment de la compilation via des décorateurs, mais non sans un peu d'aide :

export const override = <P extends Function>() => <K extends keyof P["prototype"]>(
  target: Object,
  methodName: K,
  descriptor: TypedPropertyDescriptor<P["prototype"][K]>
) => {
  // this is a no-op. The checking is all performed at compile-time, so runtime checks are not needed.
}

class Bar {
  biz (): boolean {
    return true;
  }

  qux (): string {
    return "hi";
  }
}

class Foo extends Bar {
  // this is fine
  @override<typeof Bar>()
  biz (): boolean {
    return false;
  }

  // error: type '() => number' is not assignable to type '() => boolean'
  @override<typeof Bar>()
  biz (): number {
    return 5;
  }

  // error: argument of type '"baz"' is not assignable to parameter of type '"biz" | "qux"'
  @override<typeof Bar>()
  baz (): boolean {
    return false;
  }
}

Je ne sais pas s'il est possible d'obtenir une référence au super type sans le transmettre explicitement. Cela entraîne un coût d'exécution minime, mais la vérification est tout le temps de la compilation.

@alangpierce

Mon impression est qu'une règle TSLint serait de loin la voie la plus fluide ici.

[...]

Ensuite, il pourrait peut-être être ajouté en tant que règle TSLint de base, et cela fournirait peut-être suffisamment de clarté et de motivation pour justifier l'avantage esthétique d'un mot clé de remplacement dans le langage de base.

100 % d'accord. Commençons déjà !

Salut - un peu plus tard que prévu mais voici notre règle tslint implémentée à l'aide d'un décorateur.

https://github.com/bet365/override-linting-rule

Nous l'utilisons dans notre base de code TypeScript ~ 1mloc depuis quelques années et l'avons récemment mis à jour pour utiliser le service de langage, ce qui le rend beaucoup plus rapide à exécuter et plus facile à ouvrir (l'itération précédente nécessitait une passe complète du projet recueillir les informations patrimoniales).

Pour notre cas d'utilisation, cela a été inestimable - bien que nous soyons toujours d'avis que la responsabilité devrait finalement incomber au compilateur et qu'en tant que telle, la règle de lint doit être considérée comme un palliatif.

Merci

Je suis d'accord. Je pense également qu'une solution impliquant le compilateur TS serait bien meilleure que les décorateurs et les règles de charpie.

Quel est l'état de cette fonctionnalité ? A-t-il déjà été revisité en tant que fonctionnalité de compilateur ?

Ainsi, il est possible de vérifier le type au moment de la compilation via des décorateurs, mais non sans un peu d'aide :

Quelques améliorations :

function override< Sup >( sup : { prototype : Sup } ) {
    return <
        Field extends keyof Sup ,
        Proto extends { [ key in Field ] : Sup[ Field ] } ,
    >(
        proto : Proto ,
        field : Field ,
        descr : TypedPropertyDescriptor< Sup[ Field ] > ,
    )=> {}
}

class Foo {

    bar( a : number ) {
        return a 
    }

    bar2( a : number , b : number ) {
        return a 
    }

}

class Foo2 {

    @override( Foo )
    bar( a : number ) {
        return 1
    }

    @override( Foo )
    bar2( a : number , b : number ) {
        return 1 
    }

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

    @override( Foo ) // OK
    bar( a : number ) { return 5 }

    @override( Foo ) // Error: less args than should
    bar2( a : number ) { return 5 }

    @override( Foo ) // Error: accidental override Foo2
    xxx() { return '666' }

    @override( Foo ) // Error: override of absent method
    yyy() { return 0 }

}

Terrain de jeux

Ok, j'ai trouvé une solution de remplacement qui empêche l'erreur de compilation.
Exemple avec le flux Node Readable :

// Interface so you will keep typings for all Readable methods/properties that are not overriden:
// Fileds that are `Omit`-ed should be overriden (with any signature you want, it do not have to be compatible with parent class)
interface ReadableObjStream<T> extends Omit<stream.Readable, 'push' | 'read'> {}

// Use extends (TYPE as any) to avoid compilation errors and override `Omit`-ted methods
class ReadableObjStream<T> extends (stream.Readable as any) {
    constructor()  {
        super({objectMode: true}); // force object mode. You can merge it with original options
    }
    // Override `Omit`-ed methods with YOUR CUSTOM SIGNATURE (can be non-comatible with parent):
    push(myOwnNonCompatibleSignature: T): string  { /* implementation*/ };
    read(options_nonCompatibleSignature: {opts: keyof T} ): string  { /* implementation*/ }
}

let typedReadable = new ReadableObjMode<{myData: string}>();
typedReadable.push({something: 'else'}); // will throw compilation error as expected
typedReadable.pipe(...) // non overloaded methods typings supported as expected

Le seul inconvénient de cette solution est le manque de typages lors de l'appel super.parentMethod (mais grâce au interface ReadableObjStream vous avez tous les typages lors de l'utilisation de l'instance de ReadableObjStream.

@nin-jin @bioball Merci pour les contributions avec la vérification au moment de la compilation du décorateur.

Malheureusement, cela ne semble pas fonctionner avec les membres protected , seulement avec les membres public .

(exemple d'erreur dans mon fork du terrain de jeu de nin-jin

Le spécificateur de remplacement était une fonctionnalité qui tue dans c++ 11.
Cela aide et protège tellement le code de refactorisation.
J'aimerais vraiment avoir cela dans le support de base TS sans aucun obstacle (VOTEZ !)

Nous avons définitivement montré un support sauvage pour cette fonctionnalité, mais il semble qu'ils ne prévoient toujours pas d'ajouter cette fonctionnalité de si tôt.

Une autre idée:

class Obj { 

    static override<
        This extends typeof Obj,
        Over extends keyof InstanceType<This> = never,
    >(this: This, ...overs: Over[]) { 
        return this as This & (
            new(...a:any[])=> InstanceType<This> & Protect< Omit<InstanceType<This> , Over > >
        )
    }

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

class Foo2 extends Foo.override('bar') {

    foo = 2

    bar( a : number ) {
        return 1
    }

    // Error: Class 'Foo & Protect<Pick<Foo, "bar2" | "foo">>'
    // defines instance member property 'bar2',
    // but extended class 'Foo2' defines it as instance member function.
    bar2( a : number ) {
        return 1
    }

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

type Protect<Obj> = {
    [Field in keyof Obj]:
    Object extends () => any
    ? Obj[Field] & { [Protected]: true }
    : Obj[Field]
}

Lien de l'aire de jeux

J'ajoute mes deux cents ici.
Nous avons un composant angulaire typique, qui gère les formulaires et doit se désinscrire de valueChanges et autres. Nous ne voulions pas répéter le code partout (un peu l'état actuel), j'ai donc créé un "TypicalFormBaseComponent" qui (entre autres) implémente les méthodes angulaires OnInit et OnDestroy. Le problème est que maintenant, si vous l'utilisez réellement et ajoutez votre propre méthode OnDestroy (ce qui est une chose assez standard à faire), vous masquez l'original et cassez le système. appeler super.OnInit le corrige, mais il n'y a actuellement aucun mécanisme pour forcer les enfants à le faire.

Si vous le faites avec le constructeur, cela vous oblige à appeler super()... Je cherchais quelque chose de similaire, j'ai trouvé ce fil, et honnêtement, je suis un peu déçu.
Implémenter "new" et "override" dans le ts pourrait être un changement radical, mais le réparer serait incroyablement simple dans pratiquement n'importe quelle base de code (il suffit d'ajouter "override" partout où il crie). Il peut aussi s'agir d'un simple avertissement .

Quoi qu'il en soit, y a-t-il un autre moyen pour moi de forcer le super appel chez les enfants ? Règle TSLint qui empêche de se cacher, ou quelque chose comme ça ?

PS : Je ne suis pas d'accord avec la politique "sans commentaire". Cela empêche les gens de jeter des exemples ici. Peut-être que vous n'implémenterez pas 'override', mais vous implémenterez quelque chose d'autre, qui s'occupe de leurs problèmes... c'est-à-dire, si vous pouvez réellement les lire.

@GonziHere Dans d'autres langages tels que C # ou Java, le remplacement n'implique pas l'obligation d'appeler la super méthode - ce n'est souvent pas le cas en fait. Les constructeurs sont des "méthodes virtuelles" spéciales et non normales.

Le mot clé override serait utilisé pour spécifier que la méthode doit déjà être définie avec une signature compatible sur la classe de base, et le compilateur pourrait l'affirmer.

Oui, mais le besoin d'override vous oblige à remarquer que la méthode existe.

@GonziHere On dirait que ce dont vous pourriez vraiment avoir besoin est de créer des classes de base abstraites avec des fonctions abstraites. Vous pouvez peut-être créer des méthodes privées _onInit et _onDestroy lesquelles vous pouvez compter, puis créer des fonctions abstraites protégées onInit et onDestroy (ou des fonctions régulières si elles sont non requis). Les fonctions privées appelleront les autres, puis se termineront normalement.

@GonziHere @rjamesnw La chose la plus sûre à faire est en fait d'appliquer d'une manière ou d'une autre que onInit et onDestroy sont _final_, et de définir des méthodes de modèle abstraites protégées vides que les méthodes finales appellent. Cela garantit que personne ne peut remplacer accidentellement les méthodes, et essayer de le faire (en supposant qu'il existe un moyen d'appliquer les méthodes finales) indiquerait immédiatement aux utilisateurs la bonne façon d'implémenter ce qu'ils veulent.

@shicks , @rjamesnw , je suis dans la même situation que @GonziHere. Je ne veux pas que la classe de base ait des méthodes abstraites car il y a des fonctionnalités que je veux exécuter pour chacun de ces hooks de cycle de vie. Le problème est que je ne veux pas que cette fonctionnalité de base soit accidentellement remplacée lorsque quelqu'un ajoute ngOnInit ou ngOnDestroy sur la classe enfant sans appeler super() . Exiger override attirerait l'attention du développeur sur le fait que ces méthodes existent dans la classe de base et qu'il devrait choisir s'il doit appeler super() ;

D'après mon expérience d'écriture de Java, @Override est si courant qu'il devient du bruit. Dans de nombreux cas, la méthode surchargée est abstraite ou vide (où super() ne devrait _pas_ être appelé), ce qui fait que la présence de override n'est pas un signal particulièrement clair que super() est nécessaire.

D'après mon expérience d'écriture de Java, @Override est si courant qu'il devient du bruit. Dans de nombreux cas, la méthode surchargée est abstraite ou vide (où super() ne devrait _pas_ être appelé), ce qui fait que la présence de override n'est pas un signal particulièrement clair que super() est nécessaire.

Cela n'a rien à voir avec le sujet traité. Vous pourriez tout aussi bien dire d'utiliser la composition plutôt que l'héritage pour éviter le problème.

Pour moi, la partie la plus utile de override est d'obtenir une erreur lors de la refactorisation .
À l'heure actuelle, il est trop facile de renommer une méthode dans une classe de base et d'oublier de renommer celles qui la remplacent.
Se souvenir d'appeler super n'est pas si important. Il est même correct de ne pas l'appeler dans certains cas.

Je pense qu'il y a un malentendu sur la raison pour laquelle le _override_ serait important. Pas pour forcer quelqu'un à appeler la méthode _super()_, mais pour lui faire prendre conscience qu'il en existe une et lui permettre de décider consciencieusement s'il veut supprimer son comportement ou l'étendre.

Le modèle que j'utilise pour garantir l'exactitude consiste à utiliser Parameters et ReturnType tout en se référant très explicitement à la classe de base, quelque chose comme ceci :

class Base {
    public methodName(arg1: string, arg2: number): boolean {
        return false; // base behaviour, may be stub.
    }
}
class Derived extends Base {
    public methodName(...args: Parameters<Base["methodName"]>): ReturnType<Base["methodName"]> {
        const [meaningful, variableNames] = args;
        return true; // implemented behaviour here.
    }
}

Ce type de modèle est à la base de ma suggestion d'ajouter un mot-clé inherit . . Cela garantit que toutes les mises à jour de la classe de base se propagent automatiquement et si elles sont invalides dans la classe dérivée, une erreur est donnée, ou des changements de nom de la classe de base, une erreur est également donnée, également avec l'idée inherit vous n'êtes pas contraint de n'offrir qu'une signature identique à la classe de base mais pouvez intuitivement l'étendre.

Je pense aussi qu'il y a un malentendu sur la raison pour laquelle override serait important, mais je ne comprends pas vraiment tous ces problèmes avec "si super() est nécessaire".

Pour faire écho à ce que @lorenzodallavecchia et l'auteur original du problème ont dit, marquer une fonction comme override est un mécanisme pour éviter les erreurs qui se produisent lorsque la superclasse est refactorisée, en particulier lorsque le nom et/ou la signature de la fonction remplacée change ou la fonction est supprimée.

Gardez à l'esprit que la personne qui modifie la signature de la fonction remplacée peut ne pas savoir que des remplacements existent. Si (par exemple en C++) les remplacements ne sont pas explicitement marqués comme des remplacements, alors changer le nom/signature de la fonction remplacée n'introduit pas d'erreur de compilation, cela fait simplement que ces remplacements ne sont plus des remplacements. (et .. probablement ne plus être utile, ne plus être appelé par le code qui les appelait, et introduire un tas de nouveaux bogues parce que les choses qui sont censées appeler les remplacements appellent maintenant l'implémentation de base)

Le mot-clé override, tel qu'implémenté en C++, évite à la personne qui modifie l'implémentation de base de ces problèmes, car immédiatement après leur refactorisation, les erreurs de compilation leur indiqueront qu'il existe un tas de remplacements (qui remplacent une implémentation de base désormais inexistante) et un indice eux dans le fait qu'ils ont probablement besoin de refactoriser les remplacements ainsi.

Le modificateur override présente également des avantages secondaires en matière d'utilisation de l'IDE (également mentionné par l'auteur original), à savoir qu'en tapant le mot override , l'IDE peut utilement vous montrer un tas de possibilités de ce que peut être outrepassé.

Avec le mot-clé override , il serait bon d'introduire également un indicateur qui, lorsqu'il est activé, exige que toutes les méthodes qui remplacent une autre utilisent le mot-clé override , afin d'éviter les cas où vous créez un méthode dans une sous-classe et à l'avenir la classe de base (qui pourrait être dans une bibliothèque tierce) crée une méthode avec le même nom que la méthode que vous avez créée dans la sous-classe (ce qui pourrait causer des bogues maintenant, car la sous-classe avait remplacé cette méthode ).

Obtenir une erreur de compilation dans ce cas, indiquant que vous devez ajouter le mot-clé override dans cette méthode (même si vous ne pouvez pas réellement remplacer la méthode, changez simplement son nom afin de ne pas remplacer la méthode nouvellement créée dans la classe de base), serait bien meilleure et éviterait d'éventuels bogues d'exécution.

D'après mon expérience d'écriture de Java, @Override est si courant qu'il devient du bruit.

Après de nombreuses années Java, je suis fortement en désaccord avec la déclaration ci-dessus. Le remplacement est un moyen de communication, comme les exceptions vérifiées. Il ne s'agit pas d'aimer ou de ne pas aimer, mais de qualité et d'attentes.

Et donc un gros +1 à @lucasbasquerotto , mais d'un autre point de vue : si j'introduis une méthode dans une sous-classe je veux avoir une sémantique claire de surcharge ou pas de surcharge. Par exemple, si je veux remplacer une méthode, je veux avoir un moyen de le dire explicitement. Si quelque chose ne va pas avec ma mise en œuvre, par exemple une faute de frappe ou un mauvais copier-coller, je souhaite obtenir des commentaires à ce sujet. Ou d'une autre manière : si je ne m'attends pas à remplacer, je veux aussi des commentaires, si le remplacement se produit occasionnellement.

Commentaires d'un autre développeur Java... @override est la seule fonctionnalité qui me manque vraiment dans Typescript.

J'ai environ 15 ans d'expérience en Java et 1 à 2 ans ou Typescript donc assez à l'aise avec les deux.

Le zen de @override est que vous pouvez supprimer une méthode d'une interface et la compilation s'arrêtera.

C'est comme l'inverse de la liaison serrée pour les nouvelles méthodes. Il vous permet de supprimer les anciens.

Si vous implémentez une interface et ADD une méthode, la compilation échouera mais il n'y a pas d'inverse.

Si vous supprimez une méthode, vous vous retrouverez avec du code mort.

(bien que cela puisse être réparé par un linter)

Pour être clair, quand j'ai dit que @Override était du bruit, je faisais spécifiquement référence au commentaire selon lequel il s'agissait d'un signal que vous devez appeler super . La vérification qu'il fournit est précieuse, mais pour toute méthode surchargée donnée, il s'agit de savoir s'il est nécessaire ou inutile d'appeler super . Puisqu'une si grande fraction de méthodes surchargées _ne devrait pas_, c'est inefficace à cette fin.

Tout ce que je veux dire, c'est que si vous voulez vous assurer que les sous-classes rappellent vos méthodes remplaçables, le seul moyen efficace de le faire est de faire en sorte que la méthode final (voir # 33446, entre autres) et de la faire appeler dans un Méthode de modèle vide _nommée différemment_ qui peut être remplacée en toute sécurité _sans_ l'appel super . Il n'y a tout simplement pas d'autre moyen raisonnable d'obtenir cet invariant. Je suis complètement en faveur de override , mais en tant que personne qui a été brûlée par des utilisateurs sous-classant incorrectement mes API (et je suis sur le point de ne pas les casser), je pense qu'il vaut la peine de porter l'attention sur final comme solution correcte à ce problème, plutôt que override comme cela a été suggéré en amont.

Je suis un peu confus quant à la raison pour laquelle les gens continuent de suggérer l'un des final ou override plutôt que l'autre. Nous voulons sûrement les deux , car ils résolvent des problèmes différents.

Passer outre
Empêche les personnes qui étendent une classe d'écraser accidentellement une fonction avec la leur, cassant des choses dans le processus sans même le savoir. Le fait d'avoir un mot-clé override permet au développeur de savoir qu'il remplace en fait une fonction existante, puis il peut choisir de renommer sa fonction ou d'utiliser le remplacement (et ensuite il peut savoir s'il doit appeler super).

Final
Empêchez les personnes qui étendent une classe d'écraser complètement une fonction, afin que le créateur d'origine de la classe puisse garantir un contrôle complet.

@sam-s4s Nous voulons aussi la définition :-)

Exemple de problème..

Initiale

class Base {}
class Entity extends Base {
    id() {
        return 'BUG-123' // busisess entity id
    }
}

Classe de base refactorisée

class Base {
    id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    id() {
        return '12' // busisess entity id
    }
}

Nous avons eu une surcharge accidentelle ici.

Cas avec le mot-clé define

class Base {
    define id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    define id() {
        return '12' // busisess entity id
    }
}

Doit être une erreur : redéfinition accidentelle.

Solution de contournement avec les décorateurs

@nin-jin n'est-il pas define identique à _not_ utilisant override ?

@sam-s4s Nous voulons aussi la définition :-)

Oh, je n'ai vu personne ici mentionner un mot-clé define - mais d'après ce que vous décrivez, je suppose que vous voulez dire comme le mot virtual en C# ?

(De plus, j'ai trouvé votre exemple un peu déroutant, car vos classes Base et Entity ne sont pas liées. Vouliez-vous dire pour Entity d'étendre Base ?)

Je suppose qu'il y a 2 scénarios...
a) vous écrivez votre classe de base, en supposant que tout sans final peut être remplacé.
b) vous écrivez votre classe de base en supposant que tout sans virtual ne peut pas être remplacé.

@sam-s4s Entity étend Base, bien sûr. :-) J'ai corrigé mon message. virtual parle d'autre chose.
@lorenzodallavecchia n'utilisant pas override est define | override pour le compilateur. L'utilisation define n'est que define et le compilateur peut vérifier cela de manière stricte.

@nin-jin Dans votre modèle, cela signifie que ne pas utiliser le mot-clé override est toujours légal, n'est-ce pas ? Par exemple:

class Base {
  myMethod () { ... }
}

class Overridden extends Base {
  // this would not fail because this is interpreted as define | override.
  myMethod () { ... }
}

Idéalement, l'exemple ci-dessus produit une erreur à moins que vous n'utilisiez le mot-clé override . Bien que j'imagine qu'il y aurait un indicateur de compilateur pour désactiver la vérification de remplacement, où la désactivation est la même chose que override | define pour toutes les méthodes

@bioball oui, c'est nécessaire pour la compatibilité avec beaucoup de code existant.

@sam-s4s Entity étend Base, bien sûr. :-) J'ai corrigé mon message. virtual parle d'autre chose.

Je ne suis toujours pas sûr de ce que vous entendez par définir... c'est la définition de virtual en C# :

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.

Peut-être que vous voulez dire le contraire, où, au lieu d'avoir besoin de marquer la fonction comme virtual pour remplacer, vous pouvez marquer la fonction comme define pour ensuite signifier que vous devez utiliser le override mot-clé

(pourquoi define ? Je n'ai pas vu ce mot-clé dans une autre langue)

J'ai parcouru ce sujet en quelques minutes, mais je n'ai vu personne proposer d'utiliser le remplacement comme moyen d'éviter la spécification en double des paramètres et des valeurs de retour. Par exemple:

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters) {
    }
}

Dans l'exemple ci-dessus, move aurait le même type de retour requis ( void ) et meters aurait également le même type, mais le spécifier ne serait pas nécessaire. La grande entreprise pour laquelle je travaille essaie de migrer tout notre javascript vers dactylographie, loin du typage du compilateur Closure. Cependant, dans Closure, nous pouvons simplement utiliser @override et tous les types seront déduits de la superclasse. Ceci est très utile pour réduire la possibilité de non-concordance et de réduction des doublons. On pourrait même imaginer l'implémenter de telle sorte que l'extension de la méthode avec des paramètres supplémentaires soit toujours possible, même sans spécifier de types pour les paramètres spécifiés dans la superclasse. Par exemple:

class Animal {
    move(meters:number):number {
    }
}

class Snake extends Animal {
    override move(meters, time:number) {
    }
}

La motivation pour moi de venir ici et d'écrire un commentaire est que notre entreprise exige maintenant que nous spécifions tous les types, même pour les méthodes remplacées, car Typescript n'a pas cette fonctionnalité (et nous sommes dans une longue migration). C'est assez ennuyeux.

FWIW, bien qu'il soit plus facile à écrire, omettre les types de paramètres (et d'autres instances d'inférence de type inter-fichiers) entrave la lisibilité, car cela nécessite qu'une personne peu familière avec le code creuse pour trouver où la superclasse est définie afin de savoir quel type meters est (en supposant que vous le lisez en dehors d'un IDE, ce qui est assez courant pour les projets inconnus où vous n'avez pas encore configuré d'IDE). Et le code est lu beaucoup plus souvent qu'il n'est écrit.

@shicks Vous pourriez dire cela à propos de n'importe quelle variable ou classe importée. La duplication de toutes les informations dont vous pourriez avoir besoin dans un seul fichier va à l'encontre de l'objectif d'abstraction et de modularité. Forcer les types à être dupliqués viole le principe DRY.

Cette page vous a été utile?
0 / 5 - 0 notes