C # - Les méthodes héritées du public peuvent-elles être masquées (par exemple, passer du privé à la classe dérivée)

StackOverflow https://stackoverflow.com/questions/106383

  •  01-07-2019
  •  | 
  •  

Question

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

par exemple

public DerivedClass : BaseClass {}

Je souhaite maintenant développer une méthode C dans DerivedClass qui utilise A et B. Existe-t-il un moyen de remplacer les méthodes A et B de manière privée dans DerivedClass afin que seule la méthode C soit exposée à quelqu'un qui souhaite utiliser DerivedClass? ?

Était-ce utile?

La solution

Ce n'est pas possible, pourquoi?

En C #, vous devez forcément les rendre publiques si vous héritez de méthodes publiques. Sinon, ils s'attendent à ce que vous ne tiriez pas de la classe en premier lieu.

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

Les concepteurs de langage ne le permettent pas volontairement, ce qui vous permet d'utiliser l'héritage plus correctement.

Par exemple, on peut confondre accidentellement une classe Car et une fonction dérivée d'un moteur de classe. Mais un moteur est une fonctionnalité utilisée par la voiture. Donc, vous voudriez 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. Les dérivations futures de Nor Car.

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

Que devriez-vous faire à la place?

Au lieu de cela, vous devez implémenter des interfaces. Cela vous laisse libre d'avoir des fonctionnalités utilisant la relation has-a.

Autres langues:

En C ++, vous spécifiez simplement un modificateur avant la classe de base privée, publique ou protégée. Cela rend tous les membres de la base qui étaient publics à ce niveau d'accès spécifié. Cela me semble ridicule de ne pas pouvoir 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 publics A () et B () et de lever des exceptions. Mais cela ne fait pas un cours convivial pour les gens et cela n’a pas vraiment de sens.

Autres conseils

Lorsque vous essayez par exemple d'hériter d'un List<object> et que vous souhaitez masquer le membre Add(object _ob) direct:

// 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 préférable, mais elle fait le travail. Intellisense accepte toujours, mais au moment de la compilation, vous obtenez une erreur:

  

erreur CS0619: "TestConsole.TestClass.Add (TestConsole.TestObject)" est obsolète: "Ceci n'est pas pris en charge dans cette classe."

Cela 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 IW WhateverMethodCIsAbout et que les consommateurs de DerivedClass en parlent réellement. Je ne sais rien de l’implémentation de BaseClass ou DerivedClass.

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 d’avion spécial, tel que celui qui a PairOfWings = 2 mais fait tout ce que peut faire un avion. Vous héritez de cet avion. Par ceci, vous déclarez que votre dérivation respecte le contrat de la classe de base et peut être remplacée sans clignoter partout où une classe de base est attendue. par exemple. LogFlight (Plan) continuerait à fonctionner avec une instance BiPlane.

Toutefois, si vous avez simplement besoin du comportement Fly pour un nouvel oiseau que vous souhaitez créer et que vous ne souhaitez pas prendre en charge le contrat de classe de base complet, vous composez à la place. Dans ce cas, refactorisez le comportement des méthodes à réutiliser dans un nouveau type Flight. Créez et maintenez maintenant des références à cette classe dans les plans et dans Bird. Vous n'héritez pas car Bird ne prend pas en charge l'intégralité du contrat de 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 classe de base lorsque vous remplacez. : vous pouvez remplacer et rendre publique une méthode privée de base dans la dérivation, mais pas l'inverse. par exemple. Dans cet exemple, si je dérive un type de plan & "BadPlane &"; puis remplacez et " Hide " GetPilot () - le rendre privé; une méthode cliente LogFlight (Plan p) fonctionnera pour la plupart des avions mais explosera pour & "BadPlane &"; si l'implémentation de LogFlight a besoin de / appelez GetPilot (). Étant donné que toutes les dérivations d'une classe de base sont censées être "substituables" chaque fois qu'un paramètre de classe de base est attendu, cela doit être interdit.

Le seul moyen de le faire, à ma connaissance, consiste à utiliser une relation Has-A et à implémenter uniquement les fonctions que vous souhaitez exposer.

@Brian R. Bondy m'a signalé un article intéressant sur Cacher par héritage et le nouveau mot-clé.

http://msdn.microsoft.com/ en-us / library / aa691135 (VS.71) .aspx

Donc, pour contourner le problème, je suggérerais:

class BaseClass
{
    public void A()
    {
        Console.WriteLine("BaseClass.A");
    }

    public void B()
    {
        Console.WriteLine("BaseClass.B");
    }
}

class DerivedClass : BaseClass
{
    new public void A()
    {
        throw new NotSupportedException();
    }

    new public void B()
    {
        throw new NotSupportedException();
    }

    public void C()
    {
        base.A();
        base.B();
    }
}

De cette façon, le code comme celui-ci va générer une exception NotSupportedException :

.
    DerivedClass d = new DerivedClass();
    d.A();

Cacher est une belle pente glissante. Les principaux problèmes, IMO, sont les suivants:

  • Cela dépend du moment du design type de déclaration de l'instance, ce qui signifie que si vous faites quelque chose comme BaseClass obj = new SubClass (), puis appelez obj.A (), la dissimulation est vaincue. BaseClass.A () sera exécuté.

  • Cacher peut très facilement obscurcir comportement (ou changements de comportement) dans le type de base. C'est évidemment moins une préoccupation lorsque vous possédez à la fois Si vous appelez 'base.xxx', vous faites partie de votre sous-membre.

  • Si vous possédez les deux côtés de l'équation base / sous-classe, vous devriez être en mesure de concevoir une solution plus facile à gérer que le masquage / occultage institutionnalisé.

Je dirais que si vous voulez utiliser une base de code, ce n'est pas la base de code la mieux conçue. C'est généralement le signe qu'une classe d'un niveau de la hiérarchie a besoin d'une certaine signature publique alors qu'une autre classe dérivée de cette classe n'en a pas besoin.

Un nouveau paradigme de codage s'appelle & "Composition sur l'héritage" & "; Cela déroge directement aux principes du développement orienté objet (en particulier le principe de responsabilité unique et le principe d'ouverture / fermeture).

Malheureusement, à la manière dont beaucoup de développeurs ont été formés à l'orientation objet, nous avons pris l'habitude de penser immédiatement à l'héritage plutôt qu'à la composition. Nous avons tendance à avoir de plus grandes classes qui ont de nombreuses responsabilités différentes simplement parce qu'elles peuvent être contenues dans le même & "Monde réel &"; objet. Cela peut conduire à des hiérarchies de classes de plus de 5 niveaux.

Un effet secondaire malheureux auquel les développeurs ne pensent généralement pas lorsqu'ils traitent en matière d'héritage est que celui-ci constitue l'une des formes les plus puissantes de dépendances que vous puissiez jamais introduire dans votre code. Votre classe dérivée dépend désormais fortement de la classe dont elle a été héritée. Cela peut rendre votre code plus fragile à long terme et créer des problèmes complexes, car la modification d'un comportement donné dans une classe de base rompt les classes dérivées de manière obscure.

Une des façons de casser votre code consiste à utiliser des interfaces comme indiqué dans une autre réponse. C'est une bonne chose à faire, car vous voulez que les dépendances externes d'une classe soient liées à des abstractions, pas à des types concrets / dérivés. Cela vous permet de changer d'implémentation sans changer d'interface, sans affecter une ligne de code dans votre classe dépendante.

Je préférerais avoir un système avec des centaines, des milliers, voire davantage de classes, toutes petites et faiblement couplées, que de traiter avec un système utilisant beaucoup le polymorphisme / héritage et comportant moins de classes couplées plus étroitement. .

Le livre de Robert C. Martin, Développement logiciel agile, principes, modèles et pratiques .

S'ils sont définis comme publics dans la classe d'origine, vous ne pouvez pas les remplacer par privés dans votre classe dérivée. Cependant, vous pouvez faire en sorte que la méthode publique lève une exception et implémente votre propre fonction privée.

Edit: Jorge Ferreira est correct.

Alors que la réponse à la question est & "Non &", il y a un conseil que je tiens à signaler aux autres personnes arrivant ici (étant donné que le PO faisait en quelque sorte allusion à un accès en assemblage par des tiers ). Lorsque d'autres font référence à un assemblage, Visual Studio doit respecter l'attribut suivant pour qu'il ne s'affiche pas dans intellisense (caché, mais peut TOUJOURS être appelé, alors méfiez-vous):

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

Si vous n'aviez pas d'autre choix, vous devriez pouvoir utiliser new une méthode masquant une méthode de type base, renvoyer => throw new NotSupportedException(); et la combiner avec l'attribut ci-dessus.

Une autre astuce consiste à NE PAS hériter d’une classe de base si possible, la base ayant une interface correspondante (telle que IList<T> pour List<T>). Implémentation d'interfaces & Quot; explicitement & Quot; va également cacher ces méthodes à intellisense sur le type de classe. Par exemple:

public class GoodForNothing: IDisposable
{
    void IDisposable.Dispose() { ... }
}

Dans le cas de var obj = new GoodForNothing(), la méthode Dispose() ne sera pas disponible sur obj. Cependant, il sera accessible à toute personne qui écrit explicitement le transtypage IDisposable vers <=>.

.

En outre, vous pouvez également envelopper un type de base au lieu d'en hériter, puis masquer certaines méthodes:

public class MyList<T> : IList<T>
{
    List<T> _Items = new List<T>();
    public T this[int index] => _Items[index];
    public int Count => _Items.Count;
    public void Add(T item) => _Items.Add(item);
    [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
    void ICollection<T>.Clear() => throw new InvalidOperationException("No you may not!"); // (hidden)
    /*...etc...*/
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top