J'ai toujours aimé l'idée de prendre en charge l'héritage multiple dans une langue. Le plus souvent, c'est intentionnellement abandonné et le supposé "remplaçant" est une interface. Les interfaces ne couvrent tout simplement pas la même terre sur plusieurs héritages multiples, et cette restriction peut parfois conduire à plus de code passe-partout.
La seule raison fondamentale que j'ai jamais entendue pour cela est le problème des diamants avec les classes de base. Je ne peux tout simplement pas accepter ça. Pour moi, ça vient énormément du genre: "Eh bien, il est possible de tout foirer, alors c'est automatiquement une mauvaise idée." Vous pouvez tout gâcher dans un langage de programmation, et je veux dire n'importe quoi. Je ne peux tout simplement pas prendre cela au sérieux, du moins sans une explication plus approfondie.
Le seul fait d'être conscient de ce problème représente 90% de la bataille. De plus, je pense avoir entendu parler il y a quelques années d'une solution polyvalente impliquant un algorithme "enveloppe" ou quelque chose du genre (est-ce que cela vous dit quelque chose, que ce soit?).
En ce qui concerne le problème des diamants, le seul problème potentiellement sérieux auquel je puisse penser est si vous essayez d'utiliser une bibliothèque tierce et que vous ne pouvez pas voir que deux classes apparemment non liées de cette bibliothèque ont une classe de base commune, mais en plus de La documentation, une fonctionnalité de langage simple pourrait, par exemple, exiger que vous déclariez spécifiquement votre intention de créer un diamant avant qu'il n'en compile réellement un pour vous. Avec une telle caractéristique, toute création d’un diamant est soit intentionnelle, soit imprudente, soit parce que nous ne sommes pas conscients de ce piège.
Pour que tout soit dit… Y a-t-il une raison réelle pour laquelle la plupart des gens détestent l'héritage multiple ou est-ce simplement une hystérie qui fait plus de mal que de bien? Y a-t-il quelque chose que je ne vois pas ici? Je vous remercie.
Exemple
Car étend WheeledVehicle, KIASpectra étend Car et Electronic, KIASpectra contient la radio. Pourquoi KIASpectra ne contient-il pas Electronic?
Parce qu'il est un électronique. L'héritage par rapport à la composition doit toujours être une relation is-a versus une relation has-a.
Parce qu'il est un électronique. Il y a des fils, des cartes de circuits imprimés, des commutateurs, etc.
Parce qu'il est un électronique. Si votre batterie s'épuise en hiver, vous aurez autant de problèmes que si toutes vos roues venaient à disparaître.
Pourquoi ne pas utiliser les interfaces? Prenez le numéro 3, par exemple. Je ne veux pas écrire ceci encore et encore, et je ne veux vraiment pas créer une classe d'assistance proxy bizarre pour le faire non plus:
private void runOrDont()
{
if (this.battery)
{
if (this.battery.working && this.switchedOn)
{
this.run();
return;
}
}
this.dontRun();
}
(Nous n'essayons pas de savoir si cette implémentation est bonne ou mauvaise.) Vous pouvez imaginer comment plusieurs de ces fonctions associées à Electronic peuvent ne pas être liées à WheeledVehicle, et inversement.
Je ne savais pas trop si je devais me calmer sur cet exemple, car il y avait place pour l'interprétation. Vous pouvez également penser en termes d’avion et de FlyingObject et d’Animal et de FlyingObject, ou en tant qu’exemple beaucoup plus pur.
la source
Traits
- elles agissent comme des interfaces avec une implémentation optionnelle, mais ont certaines restrictions qui aident à prévenir des problèmes tels que le problème des diamants.KiaSpectra
n'est pas unElectronic
; il a l' électronique, et peut être unElectronicCar
(qui s'étendraitCar
...)Réponses:
Dans de nombreux cas, les gens utilisent l'héritage pour donner un trait à une classe. Par exemple, pensez à un pégase. Avec l'héritage multiple, vous pourriez être tenté de dire que le Pegasus étend Cheval et Oiseau parce que vous l'avez classé comme un animal avec des ailes.
Cependant, les oiseaux ont d'autres traits que le Pegasi. Par exemple, les oiseaux pondent, Pegasi a une naissance vivante. Si l'héritage est votre seul moyen de transmettre les traits de partage, il n'y a aucun moyen d'exclure le trait de ponte du Pegasus.
Certaines langues ont choisi de faire des traits une construction explicite au sein de la langue. Other's vous guide doucement dans cette direction en supprimant MI de la langue. De toute façon, je ne peux penser à un seul cas où je me suis dit: "J'ai vraiment besoin de MI pour le faire correctement".
Aussi, discutons de ce qu'est VRAIMENT l'héritage. Lorsque vous héritez d'une classe, vous prenez une dépendance sur cette classe, mais vous devez également prendre en charge les contrats pris en charge par la classe, à la fois implicites et explicites.
Prenons l'exemple classique d'un carré héritant d'un rectangle. Le rectangle expose une propriété length et width ainsi qu’une méthode getPerimeter et getArea. Le carré remplacerait la longueur et la largeur de sorte que, lorsque l'une est définie, l'autre est définie de manière à correspondre à getPerimeter et que getArea fonctionne de la même manière (2 * longueur + 2 * largeur pour le périmètre et longueur * largeur pour la surface).
Un cas de test unique se brise si vous substituez cette implémentation d'un carré à un rectangle.
Il est assez difficile de bien faire les choses avec une seule chaîne d'héritage. Cela devient encore pire lorsque vous ajoutez un autre au mélange.
Les pièges que j'ai mentionnés avec le Pegasus in MI et les relations Rectangle / Carré résultent tous deux d'une conception inexpérimentée pour les classes. En gros, éviter les héritages multiples est un moyen d'aider les développeurs débutants à ne pas se tirer une balle dans le pied. Comme tous les principes de conception, disposer de la discipline et de la formation qui en découlent vous permet de découvrir à temps quand il convient de rompre avec eux. Voir le modèle d'acquisition de compétences Dreyfus . Au niveau expert, vos connaissances intrinsèques transcendent la confiance en des maximes / principes. Vous pouvez "sentir" quand une règle ne s'applique pas.
Et je conviens que j'ai quelque peu triché avec un exemple "réel" de la raison pour laquelle MI est mal vu.
Regardons un cadre d'interface utilisateur. Plus précisément, examinons quelques widgets qui pourraient au premier abord ressembler à une combinaison de deux autres. Comme une ComboBox. Une ComboBox est une TextBox qui a une DropDownList supportée. C'est-à-dire que je peux saisir une valeur ou sélectionner une liste de valeurs pré-ordonnée. Une approche naïve consisterait à hériter de la ComboBox de TextBox et de DropDownList.
Mais votre Textbox tire sa valeur de ce que l'utilisateur a tapé. Alors que le DDL tire sa valeur de ce que l'utilisateur sélectionne. Qui a la priorité? Le DDL peut avoir été conçu pour vérifier et rejeter toute entrée qui ne figurait pas dans sa liste de valeurs d'origine. Avons-nous outrepasser cette logique? Cela signifie que nous devons exposer la logique interne pour que les héritiers puissent remplacer. Ou pire, ajoutez à la classe de base une logique qui n’existe que pour prendre en charge une sous-classe (en violation du principe d’inversion de dépendance ).
Eviter MI vous aide à éviter ce piège. Et cela pourrait vous amener à extraire des traits communs et réutilisables de vos widgets d'interface utilisateur afin qu'ils puissent être appliqués au besoin. La propriété attachée WPF, qui permet à un élément de cadre dans WPF de fournir une propriété qu'un autre élément de cadre peut utiliser sans hériter de l'élément de cadre parent, constitue un excellent exemple .
Par exemple, une grille est un panneau de présentation dans WPF et possède des propriétés attachées Column et Row qui spécifient où un élément enfant doit être placé dans la disposition de la grille. Sans les propriétés attachées, si je veux organiser un bouton dans une grille, le bouton devrait dériver de la grille pour pouvoir accéder aux propriétés Column et Row.
Les développeurs ont poussé ce concept plus loin et ont utilisé les propriétés attachées comme moyen de comportement (par exemple, voici mon article sur la création d'un GridView triable à l'aide de propriétés attachées écrites avant que WPF n'inclue un DataGrid). L'approche a été reconnue comme un modèle de conception XAML appelé comportements attachés .
J'espère que cela a permis de mieux comprendre pourquoi l'héritage multiple est généralement mal vu.
la source
Autoriser l'héritage multiple rend les règles sur les surcharges de fonctions et la répartition virtuelle nettement plus délicates, de même que la mise en œuvre du langage autour de la disposition des objets. Celles-ci ont un impact considérable sur les concepteurs / implémenteurs de langages et soulèvent la barre déjà haute pour obtenir un langage stable et adopté.
Un autre argument commun que j’ai vu (et avancé parfois) est que, en ayant deux classes de base, votre objet viole presque toujours le principe de responsabilité unique. Soit les classes de base deux + sont de belles classes autonomes avec leur propre responsabilité (causant la violation), soit des types partiels / abstraits qui travaillent ensemble pour créer une responsabilité cohérente unique.
Dans cet autre cas, vous avez 3 scénarios:
Personnellement, je pense que l’héritage multiple a un mauvais coup, et qu’un système bien fait de composition de style de trait serait vraiment puissant / utile… mais il y a beaucoup de façons de le mal mettre en œuvre, pas une bonne idée dans un langage comme C ++.
[edit] en ce qui concerne votre exemple, c'est absurde. Une Kia a de l' électronique. Il a un moteur. De même, ses composants électroniques ont une source d'alimentation, qui se trouve être une batterie de voiture. L'héritage, encore moins l'héritage multiple n'y a pas sa place.
la source
La seule raison pour laquelle il est interdit, c’est qu’il est plus facile pour les gens de se tirer une balle dans le pied.
Ce qui suit généralement dans ce type de discussion, ce sont des arguments pour savoir si la flexibilité d'avoir les outils est plus importante que la sécurité de ne pas se décocher. Il n’existe pas de réponse tout à fait correcte à cet argument car, comme la plupart des autres éléments de la programmation, la réponse dépend du contexte.
Si vos développeurs sont à l'aise avec MI et que MI a du sens dans le contexte de ce que vous faites, vous le manquerez cruellement dans un langage qui ne le prend pas en charge. En même temps, si l'équipe n'est pas à l'aise avec cela, ou si elle n'est pas réellement nécessaire et si les gens l'utilisent «simplement parce qu'ils le peuvent», alors c'est contre-productif.
Mais non, il n'existe pas d'argument absolument convaincant convaincant qui prouve que l'héritage multiple est une mauvaise idée.
MODIFIER
Les réponses à cette question semblent être unanimes. Par souci d’être l’avocat du diable, je donnerai un bon exemple d’héritage multiple, où ne pas le faire conduit à des piratages.
Supposons que vous concevez une application sur les marchés financiers. Vous avez besoin d'un modèle de données pour les titres. Certains titres sont des produits d’actions (actions, sociétés de placement immobilier, etc.), d’autres des emprunts (obligations, obligations de sociétés), d’autres des produits dérivés (options, contrats à terme). Donc, si vous évitez MI, vous ferez un arbre d'héritage très clair et simple. Une action héritera de l'équité, Bond héritera de la dette. Super jusqu'ici, mais qu'en est-il des dérivés? Ils peuvent être basés sur des produits de type équité ou des produits de type débit? Ok, je suppose que nous allons créer plus de branches dans notre héritage. N'oubliez pas que certains dérivés sont basés sur des produits de capitaux propres, des produits de dette ou aucun des deux. Donc, notre arbre d'héritage devient compliqué. Ensuite, l’analyste commercial nous dit que nous prenons désormais en charge les titres indexés (options d’indice, options futures d’index). Et ces éléments peuvent être basés sur les capitaux propres, la dette ou les dérivés. Cela devient désordonné! Est-ce que l'option future de mon indice dérive Equity-> Stock-> Option-> Index? Pourquoi pas Equity-> Stock-> Index-> Option? Et si un jour je trouve les deux dans mon code (Ceci est arrivé; histoire vraie)?
Le problème ici est que ces types fondamentaux peuvent être mélangés dans toute permutation qui ne dérive pas naturellement l'un de l'autre. Les objets sont définis par un est une relation, donc la composition n’a aucun sens. L'héritage multiple (ou le concept similaire de mixins) est la seule représentation logique ici.
La vraie solution à ce problème consiste à définir et à mélanger les types Équité, Dette, Dérivée, Index, en utilisant un héritage multiple pour créer votre modèle de données. Cela créera des objets qui, à la fois, auront du sens et faciliteront la réutilisation du code.
la source
Equity
et lesDebt
deux implémententISecurity
.Derivative
a uneISecurity
propriété. Il se peut que ce soit lui-même unISecurity
si approprié (je ne connais pas les finances).IndexedSecurities
contient à nouveau une propriété d’interface qui est appliquée aux types sur lesquels elle est autorisée. S'ils sont tousISecurity
, ils ont tous desISecurity
propriétés et peuvent être arbitrairement imbriquées ...Les autres réponses ici semblent entrer principalement dans la théorie. Voici donc un exemple concret en Python, simplifié, dans lequel je me suis effondré, qui nécessitait pas mal de refactoring:
Bar
a été écrit en supposant qu’il avait sa propre implémentationzeta()
, ce qui est généralement une très bonne hypothèse. Une sous-classe doit la remplacer comme il convient afin de faire la bonne chose. Malheureusement, les noms ne sont que par coïncidence identiques: ils ont fait des choses assez différentes, maisBar
appelaient maintenant l'Foo
implémentation:Il est plutôt frustrant de constater qu’aucune erreur n’est générée, que l’application commence à agir de manière légèrement erronée et que le changement de code qui l’a provoquée (création
Bar.zeta
) ne semble pas être à l’origine du problème.la source
super()
?super()
a environBang
enBar, Foo
résoudrait également le problème - mais votre argument est bon, +1.Bar.zeta(self)
.Je dirais qu'il n'y a pas de réel problème avec MI dans la bonne langue . La clé consiste à autoriser les structures en diamant, mais nécessite que les sous-types fournissent leur propre remplacement, au lieu que le compilateur choisisse l'une des implémentations en fonction d'une règle.
Je le fais en goyave , une langue sur laquelle je travaille. Une caractéristique de Guava est que nous pouvons invoquer l'implémentation d'une méthode d'un super-type spécifique. Il est donc facile d'indiquer quelle implémentation de supertype doit être "héritée", sans syntaxe particulière:
Si nous ne donnions pas
OrderedSet
les siennestoString
, nous aurions une erreur de compilation. Pas de surprises.Je trouve que MI est particulièrement utile pour les collections. Par exemple, j'aime bien utiliser un
RandomlyEnumerableSequence
type pour éviter de déclarergetEnumerator
pour les tableaux, deques, etc.:Si nous n'avions pas MI, nous pourrions en écrire un
RandomAccessEnumerator
pour plusieurs collections, mais le fait d'écrire unegetEnumerator
méthode brève ajoute toujours un passe-partout.De même, MI est utile pour héritant des implémentations standard de
equals
,hashCode
ettoString
pour les collections.la source
toString
le comportement de celui-ci. Par conséquent, le fait de passer outre son comportement n'enfreint pas le principe de substitution. Vous obtenez parfois aussi des "conflits" entre des méthodes qui ont le même comportement mais des algorithmes différents, en particulier avec des collections.Ordered extends Set and Sequence
aDiamond
? C'est juste une jointure. Il manque lehat
sommet. Pourquoi appelez-vous cela un diamant? J'ai demandé ici mais cela semble une question tabou. Comment avez-vous su que vous deviez appeler cette structure triangualar un Diamond plutôt que Join?Top
type implicite qui déclaretoString
, donc il y avait en fait une structure de diamant. Mais je pense que vous avez un point: une "jointure" sans structure en losange crée un problème similaire, et la plupart des langues traitent les deux cas de la même manière.L'héritage, multiple ou autre, n'est pas si important. Si deux objets de types différents sont substituables, c'est ce qui compte, même s'ils ne sont pas liés par un héritage.
Une liste chaînée et une chaîne de caractères ont peu en commun et ne doivent pas nécessairement être liées par héritage, mais il est utile si je peux utiliser une
length
fonction pour obtenir le nombre d'éléments dans l'un ou l'autre.L'héritage est une astuce pour éviter une implémentation répétée du code. Si l'héritage vous permet d'économiser du travail et que l'héritage multiple vous épargne encore plus de travail par rapport à l'héritage simple, c'est toute la justification nécessaire.
Je soupçonne que certaines langues n'implémentent pas très bien l'héritage multiple, et pour les praticiens de ces langues, c'est ce que signifie l'héritage multiple. Mentionnez plusieurs héritages à un programmeur C ++, et ce qui me vient à l’esprit est une question qui se pose lorsqu’une classe se retrouve avec deux copies d’une base via deux chemins d’héritage différents, et s’il faut utiliser
virtual
sur une classe de base et la confusion entourant l’appel des destructeurs. , etc.Dans de nombreuses langues, l'héritage de classe est confondu avec l'héritage de symboles. Lorsque vous dérivez une classe D d’une classe B, non seulement vous créez une relation de type, mais, comme ces classes servent également d’espaces de noms lexicaux, vous devez importer des symboles de l’espace de noms B dans l’espace de noms D, en plus de la sémantique de ce qui se passe avec les types B et D eux-mêmes. L'héritage multiple pose donc des problèmes de conflit de symboles. Si nous héritons de
card_deck
etgraphic
qui ont "une"draw
méthode, que signifie-t-elle pourdraw
l'objet résultant? Un système d'objet qui n'a pas ce problème est celui de Common Lisp. Peut-être pas par coïncidence, l’héritage multiple est utilisé dans les programmes Lisp.Mal implémenté, tout ce qui dérange (comme l'héritage multiple) devrait être détesté.
la source
length
opération est utile indépendamment du concept d'héritage (ce qui dans ce cas ne sert à rien: nous ne pourrons probablement rien obtenir en essayant de partager l'implémentation entre les deux méthodes de lalength
fonction). Il peut néanmoins y avoir un héritage abstrait: par exemple, list et string sont de type séquence (mais ne fournissent aucune implémentation).Pour autant que je sache, une partie du problème (en plus de rendre votre conception un peu plus difficile à comprendre (néanmoins plus facile à coder)) est que le compilateur va économiser suffisamment d’espace pour vos données de classe, ce qui permet une énorme quantité de données. perte de mémoire dans le cas suivant:
(Mon exemple n'est peut-être pas le meilleur, mais essayez de comprendre l'essentiel de l'espace mémoire multiple dans le même but, c'était la première chose qui me vint à l'esprit: P)
Concider un DDD où le chien de la classe s’étend de caninus et animal de compagnie, un caninus a une variable qui indique la quantité de nourriture qu’il devrait manger (un entier) sous le nom dietKg, mais un animal de compagnie a aussi une autre variable à cette fin habituellement la même chose. name (sauf si vous définissez un autre nom de variable, vous aurez alors à codifier du code supplémentaire, qui était le problème initial que vous vouliez éviter, pour gérer et conserver l’intégrité des variables de base), vous disposerez alors de deux espaces mémoire pour le paramètre exact. Dans le même but, pour éviter cela, vous devrez modifier votre compilateur afin de reconnaître ce nom sous le même espace de noms et simplement affecter un seul espace mémoire à ces données, ce qui est malheureusement impossible à déterminer au moment de la compilation.
Vous pouvez, bien sûr, concevoir une langue pour spécifier qu'une telle variable peut déjà avoir un espace défini ailleurs, mais le programmeur doit spécifier à la fin où se trouve cet espace mémoire auquel cette variable fait référence (et encore du code supplémentaire).
Croyez-moi, les gens qui appliquent cette pensée très durement à propos de tout cela, mais je suis heureux que vous ayez posé la question, votre perspective est celle qui change les paradigmes;), et notez ceci, je ne dis pas que c'est impossible (mais beaucoup de suppositions un compilateur multiphasé doit être implémenté, et un très complexe), je dis simplement qu’il n’existe pas encore, si vous démarrez un projet pour votre propre compilateur capable de faire "ceci" (héritage multiple), faites-le moi savoir, je serai heureux de rejoindre votre équipe.
la source
Pendant un certain temps, je ne me suis jamais vraiment rendu compte à quel point certaines tâches de programmation étaient totalement différentes des autres - et à quel point il était utile que les langages et les modèles utilisés soient adaptés à l'espace du problème.
Lorsque vous travaillez seul ou principalement isolé sur du code que vous avez écrit, c'est un espace de problème complètement différent de l'héritage d'une base de code de 40 personnes en Inde qui y ont travaillé pendant un an avant de vous le remettre sans aucune aide transitoire.
Imaginez que vous veniez d'être embauché par la société de vos rêves et que vous héritiez ensuite d'une telle base de code. De plus, imaginez que les consultants aient appris sur l'héritage et l'héritage multiple (et qu'ils en aient été séduits) ... Pourriez-vous imaginer ce sur quoi vous pourriez travailler?
Lorsque vous héritez du code, la caractéristique la plus importante est sa compréhensibilité et le fait que les éléments sont isolés afin de pouvoir être travaillés indépendamment. Bien sûr, lorsque vous écrivez pour la première fois dans le code, des structures telles que l'héritage multiple peuvent vous faire économiser un peu de duplication et semblent correspondre à votre état d'esprit logique à ce moment-là, mais le gars suivant a juste plus de choses à démêler.
Chaque interconnexion dans votre code rend également plus difficile la compréhension et la modification de pièces de manière indépendante, encore plus avec un héritage multiple.
Lorsque vous travaillez au sein d’une équipe, vous souhaitez cibler le code le plus simple possible sans aucune logique redondante (c’est ce que signifie réellement DRY, non que vous ne deviez pas taper trop, mais que vous n’aviez jamais à changer de code 2 endroits pour résoudre un problème!)
Il existe des moyens plus simples d'obtenir du code DRY que l'héritage multiple. Par conséquent, l'inclure dans une langue ne vous ouvre que des problèmes insérés par d'autres personnes qui ne sont peut-être pas au même niveau de compréhension que vous. Il n’est même tentant que votre langue ne soit pas en mesure de vous offrir un moyen simple / moins complexe de conserver votre code au sec.
la source
Le principal argument contre l'héritage multiple est que certaines capacités utiles peuvent être fournies et certains axiomes utiles peuvent être conservés, dans un cadre qui le restreint sévèrement (*), mais ne peuvent pas être fournis et / ou conservés sans ces restrictions. Parmi eux:
La possibilité d'avoir plusieurs modules compilés séparément inclut des classes qui héritent des classes d'autres modules, et recompile un module contenant une classe de base sans avoir à recompiler chaque module qui hérite de cette classe de base.
Possibilité pour un type d'hériter d'une implémentation membre d'une classe parent sans que le type dérivé ait à le réimplémenter
L'axiome selon lequel une instance d'objet peut être convertie en amont ou en aval directement sur elle-même ou sur l'un de ses types de base, et ces retransmissions et downcasts, quelle que soit leur combinaison ou leur séquence, préservent toujours l'identité
L'axiome selon lequel si une classe dérivée substitue et enchaîne à un membre de la classe de base, le membre de base sera invoqué directement par le code de chaînage et ne sera invoqué nulle part ailleurs.
(*) Généralement, en exigeant que les types prenant en charge l'héritage multiple soient déclarés comme "interfaces" plutôt que comme classes, et en ne permettant pas aux interfaces de faire tout ce que les classes normales peuvent faire.
Si l'on souhaite autoriser l'héritage multiple généralisé, quelque chose d'autre doit céder la place. Si X et Y héritent tous deux de B, ils écrasent tous deux le même membre M et la chaîne vers l'implémentation de base, et si D hérite de X et Y mais ne remplace pas M, alors, étant donné une instance q de type D, que devrait (( B) q) .M () faire? Le fait de refuser une telle distribution violerait l'axiome selon lequel tout objet peut être converti en un type de base quelconque, mais tout comportement éventuel de la diffusion et de l'invocation du membre violerait l'axiome relatif à l'enchaînement de méthodes. On pourrait exiger que les classes ne soient chargées qu'en combinaison avec la version particulière d'une classe de base pour laquelle elles ont été compilées, mais cela est souvent délicat. Si le moteur d'exécution refuse de charger tout type dans lequel n'importe quel ancêtre peut être atteint par plusieurs routes, cela pourrait fonctionner, mais limiterait grandement l'utilité de l'héritage multiple. Autoriser les chemins d’héritage partagé uniquement en l’absence de conflits créerait des situations dans lesquelles une ancienne version de X serait compatible avec un ancien ou un nouveau Y, et un ancien Y serait compatible avec un ancien ou un nouveau X, mais les nouveaux X et nouveaux Y être compatibles, même si rien de tout ce qui, en soi, ne devrait être considéré comme un changement décisif.
Certains langages et structures autorisent l'héritage multiple, en partant du principe que les avantages de l'IM sont plus importants que ce qu'il faut abandonner pour le permettre. Cependant, les coûts de l'IM sont importants et, dans de nombreux cas, les interfaces fournissent 90% des avantages de l'IM pour une petite fraction du coût.
la source
FirstDerivedFoo
le remplacementBar
n'est pas chaîné sur un non-protégéFirstDerivedFoo_Bar
, et queSecondDerivedFoo
le remplacement est protégé par un non-virtuelSecondDerivedBar
, et si le code qui veut accéder à la méthode de base utilise ces méthodes non virtuelles protégées, pourrait être une approche réalisable ...base.VirtualMethod
), mais je ne connais aucun support linguistique pour faciliter une telle approche. Je souhaiterais qu'il existe un moyen propre de lier un bloc de code à une méthode protégée non virtuelle et à une méthode virtuelle avec la même signature sans nécessiter une méthode virtuelle "one-liner" (qui prend finalement beaucoup plus d'une ligne dans le code source).