Empêcher l’inheritance des méthodes

J’ai une classe de base Foo qui est concrète et contient 30 méthodes pertinentes pour ses sous-classes.

Maintenant, j’ai rencontré une situation spécifique à la classe de base et je veux créer une méthode qui ne peut pas être héritée, est-ce possible?

Class Foo { /* ... inheritable methods ... */ /* non-inheritable method */ public bool FooSpecificMethod() { return true; } } Class Bar : Foo { /* Bar specific methods */ } var bar = new Bar(); bar.FooSpecificMethod(); /* is there any way to get this to throw comstackr error */ 

MODIFIER

Je ne suis pas sûr d’avoir été clair à l’origine.

Je comprends les principes d’inheritance et le principe de substitution de Liskov. Dans ce cas, il y a une seule exception qui traite UNIQUEMENT du cas “non hérité” et je ne voulais donc pas créer de sous-classe “uninheritedFoo”.

Je demandais s’il est techniquement possible de créer une situation où foo.FooSpecificMethod () est une méthode valide et accessible au public, mais subclassoffoo.FooSpecificMethod () génère une erreur de compilation.

En gros, je veux une méthode scellée sur une classe non scellée.

Je repenserais la nécessité de cela.

Si vous utilisez l’inheritance, vous suggérez que “Bar” EST UN “Foo”. Si “Bar” est toujours un “Foo”, les méthodes qui fonctionnent sur “Foo” doivent également fonctionner sur “Bar”.

Si ce n’est pas le cas, je retravaillerais cela comme une méthode privée. Publiquement, Bar devrait toujours être un Foo.


Juste pour aller plus loin –

Si vous pouviez le faire, les choses deviendraient très compliquées. Vous pourriez avoir des situations où:

 Foo myBar = new Bar(); // This is legal myBar.FooSpecificMethod(); // What should this do? // It's declared a Foo, but is acutally a Bar 

Vous pouvez toutefois forcer ce comportement en utilisant la reflection. Je pense que c’est une mauvaise idée, mais FooSpecificMethod () pourrait en vérifier le type, et si ce n’est pas typeof (Foo), lève une exception. Ce serait très déroutant et aurait une très mauvaise odeur.


Modifier en réponse à la question de la question:

Il n’existe aucun moyen pour le compilateur d’appliquer ce que vous demandez. Si vous voulez vraiment forcer le compilateur à vérifier ceci et à l’empêcher, vous devriez vraiment envisager de faire de Foo une classe scellée. Vous pouvez utiliser d’autres méthodes d’extension que le sous-classement dans ce cas.

Par exemple, vous pouvez envisager d’utiliser des événements ou des delegates pour étendre le comportement au lieu de permettre à l’object d’être des sous-classes.

Essayer de faire ce que vous accomplissez, c’est essentiellement essayer d’empêcher les objectives principaux de l’inheritance.

Brian a raison sur Liskov Substitution (à la mode). Et Reed a raison sur “est-un” (également à la mode); en fait, ils vous disent tous les deux la même chose.

Vos méthodes publiques sont un contrat avec les utilisateurs de votre classe Foo, disant que “vous pouvez toujours” appeler ces méthodes sur Foo.

Sous-classe Foo signifie que vous dites qu’une sous-classe, par exemple Bar, est toujours acceptable si un Foo peut être utilisé. En particulier, cela signifie que vous n’hériterez pas (nécessairement) de l’implémentation de Foo (vous pouvez le remplacer, ou Foo peut être abstrait et ne pas donner d’implémentation particulière pour une méthode).

L’inheritance de la mise en œuvre (le cas échéant) est un détail; ce dont vous héritez réellement, c’est l’interface publique, le contrat, la promesse faite aux utilisateurs qu’un Bar peut être utilisé comme un Foo.

En effet, ils peuvent même ne jamais savoir qu’ils ont un Bar, pas un Foo: si je crée une FooFactory et que j’écris son Foo * getAFoo () pour renvoyer un pointeur sur un Bar, ils ne le sauront peut-être jamais et ne devront pas nécessairement .

Lorsque vous rompez ce contrat, vous cassez l’orientation object. (Et les classes de la collection Java, en lançant des exceptions NotSupported, coupent entièrement en fin d’object – les utilisateurs ne peuvent plus utiliser les soi-disant sous-classes de manière polymorphe. C’est une conception médiocre, qui a causé des maux de tête importants à beaucoup d’utilisateurs Java, pas quelque chose à imiter. )

S’il existe une méthode publique que les sous-classes Foo ne peuvent pas utiliser, cette méthode ne devrait pas figurer dans Foo, mais dans une sous-classe Foo, et les autres sous-classes devraient dériver de Foo.

Cela ne signifie PAS que toutes les méthodes de Foo doivent être appelables sur des sous-classes. Non, je ne me contredis pas. Les méthodes non publiques ne font pas partie de l’interface publique d’une classe.

Si vous voulez une méthode dans Foo qui ne puisse pas être appelée sur Bar, et ne devrait pas être publiquement appelable dans Foo, alors rendez cette méthode privée ou protégée.

Non, cela violerait le principe de substitution de Liskov .

De manière pragmatique, vous pouvez le faire “throw NotImplementedException ()” dans Bar, ou supprimer la méthode de Foo et la déplacer vers les sous-classes auxquelles elle s’applique.

Rendre la fonction privée l’empêchera d’être appelée directement par les sous-classes.

Si vous parlez de fonctions virtuelles que vous ne souhaitez pas surcharger, marquez la fonction comme étant scellée au point où vous souhaitez “verrouiller” la fonction.

Même s’il s’agit d’une fonction privée, elle pourrait toujours être appelée par reflection.

Vous pouvez également déclarer la fonction sur une interface et implémenter explicitement l’interface sur la classe, ce qui vous obligerait à la transtyper sur l’interface pour utiliser la fonction.

Vous pouvez créer une fonction privée, puis l’appeler en utilisant la reflection. Probablement un peu à la mer. Quoi qu’il en soit, il suffit de mettre la fonction dans votre classe de base, avec des commentaires indiquant qu’elle ne doit être appelée que depuis la classe de base. Peut-être même ces gentils /// commentaires qui apparaissent avec intellisense. Ensuite, vous pourriez avoir un bogue, mais bon, vous aurez toujours des bugs, et le mieux que vous puissiez faire est de documenter cette situation pour essayer de l’éviter.

La solution que j’ai finalement utilisée créait une classe héritée interne publique. De cette façon, il est capable d’accéder aux variables privées de la classe de base (comme il le faut) et je n’ai pas besoin d’exposer publiquement aucune de ces variables ou fonctions privées (ce que je ne souhaite pas).

Merci beaucoup pour tous vos conseils, cela m’a amené à repenser exactement ce dont j’avais besoin de cet arrangement.

Il y a deux raisons pour lesquelles une classe dérivée peut cacher des membres de la classe de base sans violer le principe de substitution de Liskov:

  1. Si le membre en question est «protégé», il ne s’agit essentiellement que d’un contrat entre la classe mère et la classe dérivée. En tant que tel, il n’impose aucune obligation à la classe dérivée, sauf si requirejs par le contrat avec la société mère. Si les membres publics de la classe de base s’appuient sur un membre particulier non public d’instances externes ayant une signification particulière, une classe dérivée qui modifie le sens de ce membre pourrait violer le LSP même si le membre lui-même n’est pas public, car changer de signification pourrait modifier le comportement des membres du public qui l’utilisent. Dans les cas où une classe de base définit un membre protégé mais ne l’utilise pas, une classe dérivée peut faire ce qu’elle veut avec ce membre sans violer le LSP. Un élément clé à comprendre avec les membres protégés est que la raison pour laquelle un `ToyotaCar` ne devrait pas modifier la sémantique des membres publics de` Car` est qu’une référence à un `ToyotaCar` pourrait être donnée à un code qui attend un` Car` qu’un `ToyotaCar`. D’autre part, si `ToyotaCar` modifie le comportement d’une méthode protégée de` Car` d’une manière qui n’est exposée à aucun membre du public, le seul code susceptible de signaler une telle modification est le code qui se trouve dans l’une des `ToyotaCar`. ou un dérivé de celui-ci, et ce code va savoir qu’il a une `ToyotaCar`. Le code qui se trouve dans un autre dérivé de `Car` (par exemple, une` FordCar`) peut accéder aux membres protégés de `Car`, mais il est garanti * pas * d’accéder à ces méthodes sur une instance de` ToyotaCar`.
  2. Parfois, il est utile d’avoir un type de base ou une interface exposant un nombre de membres qui ne seront pas tous applicables à chaque type de mise en œuvre. Bien que le principe de séparation des interfaces suggère qu’il faille éviter les interfaces “évier”, il peut arriver que de telles interfaces soient plus pratiques que toute autre solution. Par exemple, certains types de classe “stream” peuvent prendre en charge différents modes d’établissement de liaison, mais pas d’autres types. Si les stream de références passent par du code qui ne se soucie pas de la négociation, il peut être plus facile d’inclure dans la classe Stream une propriété de sélection de négociation qui peut ou non être utilisable, plutôt que d’exiger du code pour essayer de transformer un élément transmis. référence à `IHandshakeSelector` et définissez` IHandshakeSelector.HandshakeMode` si c’est le cas. D’un autre côté, si une classe de stream particulière se comporte de manière identique pour tous les modes de négociation, à l’exception de `XonXoff`, qui lève une exception si le code tente de la définir, il peut être judicieux que cette classe masque la propriété` HandshakeMode`.

Je dirais que bien que cacher un membre de la classe de base soit souvent un signe de mauvaise conception, il y a des cas où cacher des membres de la classe de base serait plus approprié que de les laisser exposés; si une classe dérivée ne peut pas utiliser correctement une méthode protégée (comme ce serait le cas avec la méthode MemberwiseClone disponible pour les dérivés de List ), il n’y a aucune raison de l’exposer. Malheureusement, il n’y a pas de moyen parfaitement propre de cacher de tels membres. Le mieux que l’on puisse faire est de déclarer un membre public du même nom (en utilisant un “nouveau” modificateur) qui bloquerait tout access au membre de la classe de base. Par exemple, code pourrait définir une classe statique publique nestede nommée MemberwiseClone sans membres. L’atsortingbut EditorBrowseable peut être utilisé pour empêcher Intellisense d’afficher cette classe nestede, mais même si le code essayait d’utiliser cette classe, il ne comportait aucun membre et il était impossible de créer un emplacement de stockage de ce type. Il devrait donc être relativement inoffensif.

Vous devez considérer Composition [ http://en.wikipedia.org/wiki/Composite_pattern } sur l’inheritance si vous souhaitez obtenir ce que vous tentez de faire et exposer des méthodes à partir de l’object privé (c’est-à-dire celui dont vous héritez actuellement).

Comme d’autres l’ont souligné, ce n’est pas possible et, surtout, cela ne devrait pas être nécessaire.

J’ai rencontré le même problème quand j’avais la structure suivante:
– Une classe parent avec des propriétés de classes enfants.
– Les classes enfants exposeraient des propriétés différentes, mais toutes avaient la même procédure d’initialisation, j’ai donc placé le code dans la classe parente.

 public class Parent { public CHILD1 Child1; public CHILD2 Child2; public Parent(params args){ Child1 = new CHILD1(arg1, arg2); Child2 = new CHILD2(arg1, arg2); } //split here public Parent(object arg1, object arg2) { PropertyInfo[] list = this.GetType().GetProperties(); foreach (var pi in list) { pi.SetValue(this, BL.Method(arg1, arg2, this.GetType().Name, pi.Name) // get data } } } public class CHILD1 : Parent { public CHILD1(object arg1, object arg2) : base(arg1, arg2) { } public object C1Property1 { get; set; } public object C1Property2 { get; set; } // .. } public class CHILD2 : Parent { public CHILD2(object arg1, object arg2) : base(arg1, arg2) { } public object C2Property1 { get; set; } public object C2Property2 { get; set; } // .. } 

Maintenant, je pourrais appeler parent.Child1.C1Property1 . Mais le code autoriserait également l’appel suivant: parent.Child1.Child1.Property1 , ce qui n’aurait aucun sens et donnerait lieu à une exception car la propriété Child1.Child1 serait toujours null .

Pour résoudre ce problème, tout ce que je devais faire était de scinder le code de la classe parente en deux classes:

  • Un contenant les propriétés enfant et appelant des constructeurs enfants
  • et un avec la logique du constructeur pour remplir les propriétés.