Pourquoi C # a-t-il été créé avec des mots-clés «new» et «virtual + override» contrairement à Java?

61

En Java , il n'y a pas virtual, new, overridemots - clés pour la définition de la méthode. Ainsi, le fonctionnement d'une méthode est facile à comprendre. Si DerivedClass étend BaseClass et a une méthode portant le même nom et la même signature de BaseClass, le remplacement aura lieu lors du polymorphisme au moment de l’exécution (à condition que la méthode ne le soit pas static).

BaseClass bcdc = new DerivedClass(); 
bcdc.doSomething() // will invoke DerivedClass's doSomething method.

Maintenant , venez à C # il peut y avoir tant de confusion et difficile à comprendre comment le newou virtual+deriveou nouveau + override virtuel fonctionne.

Je ne suis pas en mesure de comprendre pourquoi, dans le monde, je vais ajouter une méthode dans le DerivedClassmême nom et la même signature BaseClasset définir un nouveau comportement, mais au polymorphisme d'exécution, la BaseClassméthode sera invoquée! (ce qui n'est pas primordial mais cela devrait être logique).

Dans le cas où virtual + overridel'implémentation logique est correcte, mais le programmeur doit penser à la méthode à donner à l'utilisateur pour autoriser le remplacement au moment du codage. Ce qui a des avantages (on n'y va pas maintenant).

Alors pourquoi en C # il y a tant d’espace pour le raisonnement non logique et la confusion. Donc, puis-je reformuler ma question comme dans quel contexte du monde réel devrais-je penser à utiliser virtual + overrideau lieu de newet aussi à utiliser newau lieu de virtual + override?


Après de très bonnes réponses, en particulier de la part d’ Omar , j’entends que les concepteurs de C # ont insisté sur le fait que les programmeurs devraient réfléchir avant de créer une méthode, ce qui est bien et corrige certaines erreurs de débutant en Java.

Maintenant, j'ai une question en tête. Comme en Java si j'avais un code comme

Vehicle vehicle = new Car();
vehicle.accelerate();

et plus tard je fais une nouvelle classe SpaceShipdérivée de Vehicle. Ensuite, je veux tout changer caren un SpaceShipobjet je dois juste changer une seule ligne de code

Vehicle vehicle = new SpaceShip();
vehicle.accelerate();

Cela ne rompra aucune de ma logique à aucun moment du code.

Mais dans le cas de C #, si SpaceShipne remplace pas la Vehicleclasse accelerateet son utilisation, newla logique de mon code sera brisée. N'est-ce pas un inconvénient?

Anirban Nag 'tintinmj'
la source
77
Vous êtes juste habitué à la façon dont Java le fait, et vous n'avez simplement pas pris le temps de comprendre les mots clés C # selon leurs propres termes. Je travaille avec C #, j'ai tout de suite compris les termes et trouve la façon dont Java le fait.
Robert Harvey
12
IIRC, C # a fait cela pour "favoriser la clarté". Si vous devez explicitement dire «nouveau» ou «substitution», cela rend clairement et immédiatement évident ce qui se passe, plutôt que de devoir chercher à savoir si la méthode annule ou non un comportement dans une classe de base. Je trouve également très utile de pouvoir spécifier les méthodes que je veux spécifier en tant que virtuelles et celles que je ne connais pas. (Java fait cela avec final; c'est juste le contraire).
Robert Harvey
15
"En Java, il n'y a pas de nouveaux mots clés virtuels, nouveaux, de substitution pour la définition de méthode." Sauf qu'il en existe @Override.
svick
3
L'annotation @svick et les mots-clés ne sont pas la même chose :)
Anirban Nag 'tintinmj'

Réponses:

86

Puisque vous avez demandé pourquoi C # l'avait fait de cette façon, il est préférable de demander aux créateurs de C #. Anders Hejlsberg, l'architecte principal de C #, a expliqué pourquoi ils avaient choisi de ne pas utiliser le mode virtuel par défaut (comme en Java) dans une interview . Vous trouverez ci-dessous des extraits pertinents.

Gardez à l'esprit que Java utilise virtuel par défaut avec le mot-clé final pour marquer une méthode comme non virtuelle. Encore deux concepts à apprendre, mais beaucoup de gens ne connaissent pas le mot-clé final ou ne l'utilisent pas de manière proactive. C # oblige l'utilisateur à utiliser virtuel et new / override pour prendre ces décisions consciemment.

Il y a plusieurs raisons. L'un est la performance . Nous pouvons observer que lorsque les gens écrivent du code en Java, ils oublient de marquer leurs méthodes comme final. Par conséquent, ces méthodes sont virtuelles. Parce qu'ils sont virtuels, ils ne fonctionnent pas aussi bien. Il y a juste une surcharge de performances associée au fait d'être une méthode virtuelle. C'est un problème.

Un problème plus important est celui des versions . Il existe deux écoles de pensée sur les méthodes virtuelles. L'école de pensée académique dit: "Tout devrait être virtuel, parce que je pourrais vouloir le remplacer un jour." L'école de pensée pragmatique, qui découle de la création d'applications réelles qui fonctionnent dans le monde réel, dit: "Nous devons faire très attention à ce que nous rendons virtuel."

Lorsque nous faisons quelque chose de virtuel sur une plate-forme, nous faisons énormément de promesses quant à son évolution future. Pour une méthode non virtuelle, nous vous promettons que lorsque vous appelez cette méthode, x et y se produiront. Lorsque nous publions une méthode virtuelle dans une API, nous ne promettons pas seulement que lorsque vous appelez cette méthode, x et y se produiront. Nous promettons également que lorsque vous substituez cette méthode, nous l'appellerons dans cette séquence particulière en ce qui concerne ces autres et que l'état sera dans tel ou tel invariant.

Chaque fois que vous dites virtuel dans une API, vous créez un hook de rappel. En tant que concepteur de structure de système d'exploitation ou d'API, vous devez faire très attention à ce sujet. Vous ne voulez pas que les utilisateurs surchargent et accrochent à un moment quelconque d'une API, car vous ne pouvez pas nécessairement faire ces promesses. Et les gens peuvent ne pas comprendre pleinement les promesses qu'ils font lorsqu'ils font quelque chose de virtuel.

L'interview aborde plus en détail la façon dont les développeurs envisagent la conception de l'héritage de classe et comment cela a conduit à leur décision.

Passons maintenant à la question suivante:

Je ne suis pas en mesure de comprendre pourquoi, dans le monde, je vais ajouter une méthode dans mon DerivedClass avec le même nom et la même signature que BaseClass et définir un nouveau comportement, mais au polymorphisme d'exécution, la méthode BaseClass sera invoquée! (ce qui n'est pas primordial mais cela devrait être logique).

Cela se produit lorsqu'une classe dérivée veut déclarer qu'elle ne respecte pas le contrat de la classe de base, mais utilise une méthode portant le même nom. (Pour ceux qui ne connaissent pas la différence entre newet overrideen C #, voir cette page MSDN ).

Un scénario très pratique est le suivant:

  • Vous avez créé une API, qui a une classe appelée Vehicle.
  • J'ai commencé à utiliser votre API et dérivé Vehicle.
  • Votre Vehicleclasse n'avait aucune méthode PerformEngineCheck().
  • Dans ma Carclasse, j'ajoute une méthode PerformEngineCheck().
  • Vous avez publié une nouvelle version de votre API et ajouté un PerformEngineCheck().
  • Je ne peux pas renommer ma méthode car mes clients sont dépendants de mon API et cela les endommagerait.
  • Ainsi, lorsque je recompile avec votre nouvelle API, C # me met en garde contre ce problème, par exemple

    Si la base PerformEngineCheck()n'était pas virtual:

    app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    Use the new keyword if hiding was intended.
    

    Et si la base PerformEngineCheck()était virtual:

    app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
    
  • Maintenant, je dois explicitement décider si ma classe étend réellement le contrat de la classe de base ou s'il s'agit d'un contrat différent mais qui se trouve être le même nom.

  • En le faisant new, je ne casse pas mes clients si la fonctionnalité de la méthode de base était différente de la méthode dérivée. Tout code référencé Vehiclene sera pas Car.PerformEngineCheck()appelé, mais le code contenant une référence Carcontinuera de voir la même fonctionnalité que celle que j'avais proposée PerformEngineCheck().

Un exemple similaire est lorsqu'une autre méthode de la classe de base peut appeler PerformEngineCheck()(en particulier dans la version la plus récente), comment l'empêche-t-elle d'appeler le PerformEngineCheck()de la classe dérivée? En Java, cette décision incomberait à la classe de base, mais elle ne sait rien de la classe dérivée. En C #, cette décision repose à la fois sur la classe de base (via le virtualmot - clé) et sur la classe dérivée (via les mots new- overrideclés et ).

Bien sûr, les erreurs que le compilateur génère constituent également un outil utile pour les programmeurs afin de ne pas commettre d'erreur inopinément (c'est-à-dire, remplacer ou fournir de nouvelles fonctionnalités sans s'en rendre compte.)

Comme Anders l’a dit, le monde réel nous force à nous attaquer à de telles questions que nous ne voudrions jamais aborder si nous partions de zéro.

EDIT: Ajout d'un exemple où il newfaudrait utiliser pour assurer la compatibilité des interfaces.

EDIT: En parcourant les commentaires, je suis également tombé sur un article écrit par Eric Lippert (alors membre du comité de conception de C #) sur d’autres exemples de scénarios (mentionnés par Brian).


PARTIE 2: Basé sur la question mise à jour

Mais dans le cas de C # si SpaceShip ne substitue pas la classe Vehicle à accélérer et à utiliser new, la logique de mon code sera brisée. N'est-ce pas un inconvénient?

Qui décide si SpaceShipest prépondérant en fait la Vehicle.accelerate()ou si elle est différente? Ce doit être le SpaceShipdéveloppeur. Ainsi, si le SpaceShipdéveloppeur décide de ne pas conserver le contrat de la classe de base, votre appel Vehicle.accelerate()ne doit pas être adressé SpaceShip.accelerate()ou doit-il l'être? C'est à ce moment qu'ils le marqueront comme new. Toutefois, s’ils décident que le contrat est effectivement conservé, ils le marqueront en fait override. Dans les deux cas, votre code se comportera correctement en appelant la méthode correcte en fonction du contrat . Comment votre code peut-il décider s'il SpaceShip.accelerate()s'agit d'une substitution Vehicle.accelerate()ou s'il s'agit d'une collision de noms? (Voir mon exemple ci-dessus).

Cependant, en cas d'héritage implicite, même si SpaceShip.accelerate()le contrat de n'est pas conservé Vehicle.accelerate(), l'appel de la méthode ira quand même à SpaceShip.accelerate().

Omer Iqbal
la source
12
Le point de performance est maintenant complètement obsolète. Pour une preuve, voir mon repère montrant qu'accéder à un champ via une méthode non finale mais jamais surchargée prend un seul cycle.
Maaartinus
7
Bien sûr, cela pourrait être le cas. La question était que lorsque C # a décidé de le faire, pourquoi l'a-t-il fait à CETTE heure, et par conséquent cette réponse est valide. Si la question est de savoir si cela a toujours un sens, c'est une discussion différente, à mon humble avis.
Omer Iqbal
1
Je suis entièrement d'accord avec vous.
Maaartinus
2
IMHO, bien qu'il existe certainement des utilisations pour des fonctions non virtuelles, le risque d'embûches inattendues se produit lorsqu'un élément qui n'est pas censé redéfinir une méthode de classe de base ou implémenter une interface le fait.
Supercat
3
@Nilzor: D'où l'argument académique vs pragmatique. De manière pragmatique, la responsabilité de casser quelque chose repose dans le dernier changemaker. Si vous modifiez votre classe de base et qu'une classe dérivée existante dépend de sa non-modification, ce n'est pas leur problème ("ils" peuvent ne plus exister). Ainsi, les classes de base sont verrouillées dans le comportement de leurs classes dérivées, même si cette classe n'aurait jamais dû être virtuelle . Et comme vous le dites, RhinoMock existe. Oui, si vous finissez correctement toutes les méthodes, tout est équivalent. Nous nous référons ici à chaque système Java jamais construit.
deworde
94

Cela a été fait parce que c'est la bonne chose à faire. Le fait est que permettre à toutes les méthodes d'être écrasées est une erreur. cela conduit au problème de la classe de base fragile, où vous n'avez aucun moyen de dire si un changement dans la classe de base va casser les sous-classes. Par conséquent, vous devez soit mettre en liste noire les méthodes qui ne doivent pas être remplacées, soit mettre en liste blanche celles qui sont autorisées à être remplacées. Parmi les deux, la liste blanche est non seulement plus sûre (car vous ne pouvez pas créer accidentellement une classe de base fragile ), elle nécessite également moins de travail car vous devez éviter les héritages en faveur de la composition.

Doval
la source
7
Autoriser toute méthode arbitraire à être remplacée est une erreur. Vous ne devriez pouvoir redéfinir que les méthodes que le concepteur de la superclasse a désignées comme sûres pour la neutralisation.
Doval
21
Eric Lippert en parle en détail dans son article, Virtual Methods et Brittle Base Classes .
Brian
12
Java a un finalmodificateur, alors quel est le problème?
Sarge Borsch
13
@corsiKa "les objets doivent être ouverts à l'extension" - pourquoi? Si le développeur de la classe de base n'a jamais pensé à l'héritage, il est presque garanti qu'il existe dans le code des dépendances et des hypothèses subtiles qui conduiront à des bogues (rappelez HashMap- vous ?). Soit concevoir pour l'héritage et clarifier le contrat ou ne pas le faire et assurez-vous que personne ne peut hériter de la classe. @Overridea été ajouté en tant que pansement car il était trop tard pour modifier le comportement à ce moment-là, mais même les développeurs Java (notamment Josh Bloch) d’origine sont d’accord pour dire que c’était une mauvaise décision.
Voo le
9
@jwenting "Opinion" est un mot amusant. les gens aiment attacher cela aux faits pour pouvoir les ignorer. Mais quoi qu'il en soit, c'est aussi "l'opinion" de Joshua Bloch (voir: Effective Java , élément 17 ), "l'opinion" de James Gosling (voir cette interview ) et, comme le souligne Omer Iqbal , c'est aussi "l'opinion" d'Anders Hejlsberg. (Et bien qu'il n'ait jamais abordé la question de l'adhésion ou du retrait, Eric Lippert convient clairement que l'héritage est également dangereux.) Alors, à qui fait-on référence?
Doval
33

Comme l'a dit Robert Harvey, tout est dans ce que vous avez l'habitude de faire. Je trouve étrange le manque de flexibilité de Java .

Cela dit, pourquoi avoir cela en premier lieu? Pour la même raison que C # a public, internal( « rien » aussi), protected, protected internalet private, mais Java vient public, protectedrien, et private. Il offre un contrôle plus fin du grain sur le comportement de ce que vous codez, au détriment du nombre de termes et de mots-clés à surveiller.

Dans le cas de newvs. virtual+override, cela ressemble à ceci:

  • Si vous voulez forcer les sous-classes à implémenter la méthode, utilisez abstract, et overridedans la sous-classe.
  • Si vous souhaitez fournir une fonctionnalité tout en permettant à la sous-classe de la remplacer, utilisez virtual-la et overridedans la sous-classe.
  • Si vous souhaitez fournir des fonctionnalités que les sous-classes ne doivent jamais remplacer, n'utilisez rien.
    • Si vous avez alors une sous - classe de cas particulier qui ne doit se comporter différemment, utiliser newdans la sous - classe.
    • Si vous voulez vous assurer qu'aucune sous-classe ne peut jamais remplacer le comportement, utilisez-la sealeddans la classe de base.

Pour un exemple concret: un projet sur lequel j'ai travaillé sur des commandes de commerce électronique traitées provenant de nombreuses sources différentes. Il y avait une base OrderProcessorqui avait la plupart de la logique, avec certaines abstract/ virtualméthodes à remplacer par la classe enfant de chaque source. Cela a bien fonctionné, jusqu'à ce que nous ayons une nouvelle source qui traitait les commandes de manière complètement différente, de sorte que nous devions remplacer une fonction essentielle. Nous avions deux choix à ce stade: 1) Ajouter virtualà la méthode de base et overrideà l'enfant; ou 2) Ajouter newà l'enfant.

Tandis que l’un ou l’autre pourrait fonctionner, le premier facilitera considérablement le remplacement de cette méthode dans le futur. Cela s'afficherait en saisie automatique, par exemple. C'était un cas exceptionnel, cependant, nous avons donc choisi d'utiliser à la newplace. Cela a préservé le standard de "cette méthode n'a pas besoin d'être remplacée", tout en permettant le cas particulier où cela s'est produit. C'est une différence sémantique qui facilite la vie.

Prenez note, toutefois, qu'il y a une différence de comportement associé à cela, non seulement une différence sémantique. Voir cet article pour plus de détails. Cependant, je ne me suis jamais trouvé dans une situation où je devais tirer parti de ce comportement.

Bobson
la source
7
Je pense que l'utilisation intentionnelle de newcette manière est un bug en attente. Si j'appelle une méthode sur une instance donnée, je m'attends à ce qu'elle fasse toujours la même chose, même si cette instance est convertie en classe de base. Mais ce n'est pas comme ça que les newméthodes ed se comportent.
svick
@svick - Potentiellement, oui. Dans notre scénario particulier, cela ne se produirait jamais, mais c'est la raison pour laquelle j'ai ajouté l'avertissement.
Bobson
newWebViewPage <TModel> du framework MVC constitue une excellente utilisation de . Mais j'ai aussi été ébranlé par un bug qui a duré des newheures, alors je ne pense pas que ce soit une question déraisonnable.
pdr
1
@ C. Champagne: N'importe quel outil peut être mal utilisé et celui-ci est un outil particulièrement tranchant - vous pouvez vous couper facilement. Mais ce n'est pas une raison pour supprimer l'outil de la boîte à outils et supprimer une option d'un concepteur d'API plus talentueux.
pdr
1
@svick Correct, c'est pourquoi il s'agit normalement d'un avertissement du compilateur. Mais avoir la possibilité de "nouvelles" couvre les conditions de bord (comme celle donnée), et même mieux, il est vraiment évident que vous faites quelque chose de bizarre lorsque vous venez de diagnostiquer l'inévitable bogue. "Pourquoi cette classe et seulement cette classe buggy ... ah-ah, un" nouveau ", allons tester où la SuperClass est utilisée".
deworde
7

La conception de Java est telle que, quelle que soit la référence à un objet, un appel à un nom de méthode particulier avec des types de paramètre particuliers, s'il est autorisé, invoque toujours la même méthode. Il est possible que le type d'une référence ait une incidence sur les conversions implicites de type paramètre, mais une fois que toutes ces conversions ont été résolues, le type de la référence n'est plus pertinent.

Cela simplifie l'exécution, mais peut causer des problèmes malheureux. Suppose GrafBasen'implémente pas void DrawParallelogram(int x1,int y1, int x2,int y2, int x3,int y3), mais l' GrafDerivedimplémente comme une méthode publique qui dessine un parallélogramme dont le quatrième point calculé est opposé au premier. Supposons en outre qu'une version ultérieure d' GrafBaseimplémente une méthode publique avec la même signature, mais son quatrième point calculé en regard du second. Les clients qui reçoivent un, GrafBasemais reçoivent une référence à GrafDerived, s'attendent DrawParallelogramà calculer le quatrième point à la manière de la nouvelle GrafBaseméthode, mais les clients qui utilisaient GrafDerived.DrawParallelogramavant que la méthode de base ait été modifiée s'attendent au comportement GrafDerivedmis en œuvre à l'origine.

En Java, il serait impossible pour l'auteur de GrafDerivedfaire coexister cette classe avec des clients qui utilisent la nouvelle GrafBase.DrawParallelogramméthode (et qui ignorent GrafDerivedmême l'existence) sans rompre la compatibilité avec le code client existant utilisé GrafDerived.DrawParallelogramauparavant GrafBase. Puisque le DrawParallelogramne peut pas dire quel type de client l'invoque, il doit se comporter de manière identique lorsqu'il est appelé par des types de code client. Étant donné que les deux types de code client ont des attentes différentes quant à la manière dont il doit se comporter, il est impossible d' GrafDerivedéviter de violer les attentes légitimes de l'un d'entre eux (c'est-à-dire de casser le code client légitime).

En C #, si GrafDerivedon ne recompilé, le moteur d' exécution suppose que le code qui invoque la DrawParallelogramméthode sur des références de type GrafDerivednous attendrons le comportement GrafDerived.DrawParallelogram()avait quand il était la dernière compilation, mais le code qui appelle la méthode sur des références de type GrafBasevont s'attendre GrafBase.DrawParallelogram(le comportement était ajouté). S'il GrafDerivedest ultérieurement recompilé en présence de la version améliorée GrafBase, le compilateur résoudra jusqu'à ce que le programmeur spécifie si sa méthode était censée remplacer le membre hérité GrafBase, ou si son comportement doit être lié à des références de type GrafDerived, mais ne doit pas remplace le comportement des références de type GrafBase.

On pourrait raisonnablement penser qu'une méthode permettant de GrafDerivedfaire quelque chose de différent d'un membre GrafBasedont la signature est identique indiquerait une mauvaise conception et ne devrait donc pas être prise en charge. Malheureusement, étant donné que l'auteur d'un type de base n'a aucun moyen de savoir quelles méthodes peuvent être ajoutées aux types dérivés, ni l'inverse, la situation dans laquelle les clients des classes de base et des classes dérivées ont des attentes différentes pour les méthodes portant le même nom est essentiellement inévitable sauf si personne n'est autorisé à ajouter un nom que quelqu'un d'autre pourrait également ajouter. La question n'est pas de savoir si une telle duplication de nom devrait avoir lieu, mais plutôt comment minimiser les dommages quand cela se produit.

supercat
la source
2
Je pense qu'il y a une bonne réponse ici, mais c'est perdu dans le mur du texte. Veuillez diviser ceci en quelques paragraphes supplémentaires.
Bobson
Qu'est-ce que DerivedGraphics? A class using GrafDerived`?
C.Champagne
@CCmpagne: je voulais dire GrafDerived. J'ai commencé à utiliser DerivedGraphics, mais je pensais que c'était un peu long. Even GrafDerivedest toujours un peu long, mais ne savait pas comment nommer les types de rendu graphique qui devraient avoir une relation base / dérivée claire.
Supercat
1
@Bobson: Mieux?
Supercat
6

Situation standard:

Vous êtes le propriétaire d'une classe de base utilisée par plusieurs projets. Vous souhaitez modifier cette classe de base, qui se situera entre 1 et d'innombrables classes dérivées, qui font partie de projets offrant une valeur réelle la chose en cours d'exécution sur le cadre). Bonne chance à tous les propriétaires occupés des classes dérivées; "eh bien, vous devez changer, vous n'auriez pas dû remplacer cette méthode" sans obtenir un représentant en tant que "Framework: Delayer of Projects and Causer of Bugs" pour les personnes qui doivent approuver les décisions.

Surtout que, en ne déclarant pas qu'il est impossible de le remplacer, vous avez implicitement déclaré qu'ils pouvaient faire ce qui empêche maintenant votre modification.

Et si vous n'avez pas un nombre significatif de classes dérivées fournissant une valeur réelle en surchargeant votre classe de base, pourquoi est-ce une classe de base en premier lieu? L'espoir est un puissant facteur de motivation, mais également un très bon moyen de se retrouver avec du code non référencé.

Résultat final: votre code de classe de base de structure devient incroyablement fragile et statique, et vous ne pouvez pas réellement apporter les modifications nécessaires pour rester à jour / efficace. Alternativement, votre framework obtient un représentant pour l’instabilité (les classes dérivées continuent à se briser) et les utilisateurs ne l’utiliseront pas du tout, car la principale raison de l’utilisation d’un framework est de rendre le codage plus rapide et plus fiable.

En termes simples, vous ne pouvez pas demander aux propriétaires de projets très occupés de retarder leur projet afin de corriger les bogues que vous introduisez, et de vous attendre à quelque chose de mieux qu'un "départ", à moins que vous ne leur apportiez des avantages significatifs, même si la "faute" d'origine était la leur, ce qui est au mieux discutable.

Mieux vaut ne pas les laisser faire la mauvaise chose en premier lieu, c'est-à-dire où "non par défaut virtuel" entre en jeu. Et quand quelqu'un vient à vous avec une raison très claire pour laquelle ils ont besoin de cette méthode particulière pour pouvoir être annulée, et pourquoi il devrait être sûr, vous pouvez le "déverrouiller" sans risquer de casser le code de quelqu'un d'autre.

deworde
la source
0

Le réglage par défaut sur non-virtuel suppose que le développeur de la classe de base est parfait. D'après mon expérience, les développeurs ne sont pas parfaits. Si le développeur d'une classe de base ne peut imaginer un cas d'utilisation où une méthode pourrait être remplacée ou oublie d'ajouter du virtuel, je ne peux pas tirer parti du polymorphisme pour étendre la classe de base sans modifier la classe de base. Dans le monde réel, modifier la classe de base n'est souvent pas une option.

En C #, le développeur de la classe de base ne fait pas confiance au développeur de la sous-classe. En Java, le développeur de la sous-classe ne fait pas confiance au développeur de la classe de base. Le développeur de la sous-classe est responsable de la sous-classe et devrait (à mon humble avis) avoir le pouvoir d'étendre la classe de base comme il l'entend (sauf interdiction explicite, et en java, ils peuvent même se tromper).

C'est une propriété fondamentale de la définition du langage. Ce n'est ni bon ni mauvais, c'est ce que c'est et ne peut pas changer.

clish
la source
2
cela ne semble rien offrir de substantiel sur les points soulevés et expliqués dans les 5 réponses précédentes
gnat