64 votes

C# - Les méthodes héritées publiquement peuvent-elles être cachées (par exemple, rendues privées à la classe dérivée) ?

Supposons que j'ai une BaseClass avec des méthodes publiques A et B, et que je crée une DerivedClass par héritage.

par exemple

public DerivedClass : BaseClass {}

Maintenant, je veux développer une méthode C dans la DerivedClass qui utilise A et B. Existe-t-il un moyen de remplacer les méthodes A et B par des méthodes privées dans la DerivedClass afin que seule la méthode C soit exposée à quelqu'un qui veut utiliser ma DerivedClass ?

77voto

Brian R. Bondy Points 141769

Ce n'est pas possible, pourquoi ?

En C#, on vous impose que si vous héritez de méthodes publiques, vous devez les rendre publiques. Sinon, on s'attend à ce que vous ne dériviez pas de la classe en premier lieu.

Au lieu d'utiliser la relation is-a, vous devriez utiliser la relation has-a.

Les concepteurs du langage ne le permettent pas exprès pour que vous utilisiez l'héritage plus correctement.

Par exemple, on pourrait accidentellement confondre une classe Car qui dérive d'une classe Engine pour obtenir sa fonctionnalité. Mais un moteur est une fonctionnalité qui est utilisée par la voiture. Il faut donc utiliser la relation has-a. L'utilisateur de la voiture ne veut pas avoir accès à l'interface du moteur. Et la voiture elle-même ne doit pas confondre les méthodes du moteur avec les siennes. Ni les futures dérivations de la voiture.

Ils ne permettent donc pas de vous protéger des mauvaises hiérarchies d'héritage.

Que devriez-vous faire à la place ?

Vous devez plutôt implémenter des interfaces. Cela vous laisse libre d'avoir des fonctionnalités en utilisant la relation has-a.

Autres langues :

En C++, il suffit de spécifier un modificateur avant la classe de base de private, public ou protected. Cela rend tous les membres de la base qui étaient publics au niveau d'accès spécifié. Il me semble idiot que l'on ne puisse pas faire la même chose en C#.

Le code restructuré :

interface I
{
    void C();
}

class BaseClass
{
    public void A() { MessageBox.Show("A"); }
    public void B() { MessageBox.Show("B"); }
}

class Derived : I
{
    public void C()
    {
        b.A();
        b.B();
    }

    private BaseClass b;
}

Je comprends que les noms des classes ci-dessus sont un peu discutables :)

Autres suggestions :

D'autres ont suggéré de rendre A() et B() publics et de lancer des exceptions. Mais cela ne fait pas une classe conviviale pour les gens et cela n'a pas vraiment de sens.

0 votes

Les interfaces sont certainement l'une des voies recommandées, mais elles ne doivent pas être la seule étape. Vous devez toujours être conscient du fait que votre code viole ou non le principe de responsabilité unique. Même l'implémentation d'interfaces ne vous empêche pas d'écrire des classes monolithiques, volumineuses et peu maniables.

0 votes

Une autre raison de ne pas cacher les méthodes publiques est que cela peut rompre les contrats d'interface avec les consommateurs. Par exemple, cacher la méthode Remove() d'une classe de base qui implémente IList casserait tout code qui s'attend à ce que cette méthode soit présente sur la base de l'interface.

7 votes

Malheureusement, l'impossibilité de faire cela signifie que lorsque vous utilisez la bibliothèque mal conçue de quelqu'un d'autre, il est impossible d'isoler votre base de code de celle-ci. Un excellent exemple est la classe WebControl de System.Web --- elle possède des tonnes de propriétés publiques qui abusent des styles en ligne, et parce qu'ils ont intégré tout cela dans la classe de base, il n'y a aucun moyen de créer une classe CleanWebControl qui hérite d'elle et cache la mauvaise conception. Cacher la mauvaise conception de quelqu'un d'autre serait utile, si c'était possible, surtout lorsque la mauvaise conception se trouve dans le .NET Framework lui-même.

31voto

nono Points 31

Lorsque vous essayez, par exemple, d'hériter d'une List<object> et que vous voulez cacher le direct Add(object _ob) membre :

// the only way to hide
[Obsolete("This is not supported in this class.", true)]
public new void Add(object _ob)
{
    throw NotImplementedException("Don't use!!");
}

Ce n'est pas vraiment la solution la plus souhaitable, mais elle fait l'affaire. Intellisense accepte toujours, mais au moment de la compilation, vous obtenez une erreur :

error CS0619 : 'TestConsole.TestClass.Add(TestConsole.TestObject)' is obsolete : 'This is not supported in this class'.

2 votes

Magnifique ! C'est ce dont j'avais besoin. J'ai une mauvaise classe mal structurée que je ne peux pas changer (appartenant à quelqu'un d'autre) qui a des membres que je dois cacher. Cela fonctionne très bien. Notez que le C++ vous permet de faire cela.

6voto

Hamish Smith Points 5961

Ça semble être une mauvaise idée. Liskov ne serait pas impressionné.

Si vous ne voulez pas que les consommateurs de DerivedClass puissent accéder aux méthodes DeriveClass.A() et DerivedClass.B(), je suggérerais que DerivedClass implémente une interface publique IWhateverMethodCIsAbout et que les consommateurs de DerivedClass parlent en fait à IWhateverMethodCIsAbout et ne sachent rien du tout de l'implémentation de BaseClass ou de DerivedClass.

0 votes

J'ai fait quelques recherches sur les interfaces et peut-être que je rate quelque chose d'important ici, mais si je définissais l'interface, je voudrais l'implémenter en utilisant les méthodes A et B de toute façon. A et B sont des fonctions d'aide pour implémenter des fonctions plus importantes dans les classes dérivées.

0 votes

Lyndon, j'espère que le code de la réponse acceptée montre clairement que DerivedClass est toujours une sous-classe de BaseClass et que vous pouvez toujours utiliser les fonctions A et B de DerivedClass pour implémenter C, mais que le monde extérieur ne peut pas interagir avec DerivedClass en tant que DerivedClass et utilise l'interface à la place.

5voto

Gishu Points 59012

Ce dont vous avez besoin, c'est de la composition, pas de l'héritage.

class Plane
{
  public Fly() { .. }
  public string GetPilot() {...}
}

Maintenant, si vous avez besoin d'un type spécial d'avion, comme celui qui a PairOfWings = 2, mais qui autrement fait tout ce qu'un avion peut faire Vous héritez de plane. Vous déclarez ainsi que votre dérivation respecte le contrat de la classe de base et peut être substituée sans sourciller partout où une classe de base est attendue. Par exemple, LogFlight(Plane) continuerait à fonctionner avec une instance de BiPlane.

Cependant, si vous avez juste besoin du comportement Fly pour un nouvel oiseau que vous voulez créer et que vous n'êtes pas prêt à supporter le contrat complet de la classe de base, vous composez plutôt. Dans ce cas, refactorez le comportement des méthodes à réutiliser dans un nouveau type Flight. Maintenant, créez et maintenez des références à cette classe à la fois dans Plane et Bird. Vous n'héritez pas car Bird ne supporte pas le contrat complet de la classe de base... ( par exemple, il ne peut pas fournir GetPilot() ).

Pour la même raison, vous ne pouvez pas réduire la visibilité des méthodes de la classe de base lorsque vous surchargez vous pouvez surcharger et rendre publique une méthode privée de base dans la dérivation, mais pas l'inverse. Par exemple, si je dérive un type d'avion "BadPlane" et que je surcharge et "cache" GetPilot() - en le rendant privé ; une méthode client LogFlight(Plane p) fonctionnera pour la plupart des avions, mais explosera pour "BadPlane" si l'implémentation de LogFlight a besoin/appelle GetPilot(). Puisque toutes les dérivations d'une classe de base sont censées être "substituables" partout où un paramètre de la classe de base est attendu, cela doit être interdit.

1 votes

La nécessité de masquer les méthodes et propriétés de la classe de base se présente lorsque vous n'avez pas le contrôle sur la source de la classe de base. Supposons que vous deviez remplacer un mécanisme entier de la classe de base qui ne répond pas à vos besoins. Vous créez vos propres méthodes, en remplacez d'autres, mais vous voulez aussi en cacher certaines qui appartiennent au mécanisme original et qui pourraient être utilisées par d'autres programmeurs. Vous pouvez choisir d'agréger/envelopper la classe "de base", mais cela n'est pas toujours acceptable car cela rompt la chaîne d'héritage. Marquer ces méthodes comme obsolètes est la meilleure solution.

3voto

Nescio Points 12613

La seule façon de le faire, à ma connaissance, est d'utiliser une relation Has-A et de n'implémenter que les fonctions que vous souhaitez exposer.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X