2009-12-09 18 views
33

Si j'ai l'exemple de code suivant:Comment cacher une propriété héritée dans une classe sans modifier la classe héritée (classe de base)?

public class ClassBase 
{ 
    public int ID { get; set; } 

    public string Name { get; set; } 
} 

public class ClassA : ClassBase 
{ 
    public int JustNumber { get; set; } 

    public ClassA() 
    { 
     this.ID = 0; 
     this.Name = string.Empty; 
     this.JustNumber = string.Empty; 
    } 
} 

Que dois-je faire pour cacher la propriété Name (Ne pas représentée en tant que membre des membres ClassA) sans modifier ClassBase?

+1

Ceci est une violation de l'un des principes de base de la POO (polymorphisme et, par conséquent, le 'L' dans SOLID). – jason

+1

Spécifiquement une violation du principe de substitution de liskov (http://en.wikipedia.org/wiki/Liskov_substitution_principle) –

+13

Beaucoup de réponses ignorent le simple fait que la classe de base peut être fournie par un tiers. Comme ce code n'appartient pas, il ne peut pas être modifié. Il est définitivement justifié de vouloir créer une version plus étroitement ciblée de la classe de base en cachant des membres. Et il est plus agréable d'hériter que d'utiliser le contrôle de base en tant que composant d'un nouveau contrôle car cela nécessite beaucoup de travail supplémentaire pour exposer les méthodes déjà trouvées sur la base. – Mario

Répondre

46

Je sens une odeur de code ici. Je suis d'avis que vous ne devriez hériter que d'une classe de base si vous implémentez toutes les fonctionnalités de cette classe de base. Ce que vous faites ne représente pas vraiment les principes orientés objet correctement. Ainsi, si vous voulez hériter de votre base, vous devriez implémenter Name, sinon vous héritez mal de votre héritage. Votre classe A devrait être votre classe de base et votre classe de base actuelle devrait hériter de A si c'est ce que vous voulez, et non l'inverse.

Cependant, ne pas s'éloigner trop loin de la question directe. Si vous avez voulez bafouer « les règles » et veulent continuer sur le chemin que vous avez choisi - voici comment vous pouvez aller à ce sujet:

La convention est de mettre en œuvre la propriété, mais jeter un NotImplementedException lorsque cette propriété est appelé - bien que je n'aime pas ça non plus. Mais c'est mon opinion personnelle et cela ne change rien au fait que cette convention est toujours en vigueur.

Si vous essayez de obsolète la propriété (et il est déclaré dans la classe de base comme virtuelle), alors vous pouvez soit utiliser l'attribut obsolète sur elle:

[Obsolete("This property has been deprecated and should no longer be used.", true)] 
public override string Name 
{ 
    get 
    { 
     return base.Name; 
    } 
    set 
    { 
     base.Name = value; 
    } 
} 

(Edit: Comme Brian souligné dans les commentaires, le deuxième paramètre de l'attribut provoquera une erreur de compilation si quelqu'un référence la propriété Name, ainsi ils ne pourront pas l'utiliser même si vous l'avez implémenté dans la classe dérivée.)

Ou comme je l'ai mentionné, utilisez NotImplementedException:

public override string Name 
{ 
    get 
    { 
     throw new NotImplementedException(); 
    } 
    set 
    { 
     throw new NotImplementedException(); 
    } 
} 

Cependant, si la propriété n'est pas déclarée comme virtuelle, vous pouvez utiliser le nouveau mot-clé pour le remplacer:

public new string Name 
{ 
    get 
    { 
     throw new NotImplementedException(); 
    } 
    set 
    { 
     throw new NotImplementedException(); 
    } 
} 

Vous pouvez toujours utiliser l'attribut obsolète de la même manière que si la méthode a été substituée, ou vous pouvez lancer l'exception NotImplementedException, selon ce que vous choisissez. Je serais probablement utiliser:

[Obsolete("Don't use this", true)] 
public override string Name { get; set; } 

ou:

[Obsolete("Don't use this", true)] 
public new string Name { get; set; } 

Selon si oui ou non elle a été déclarée comme virtuelle dans la classe de base.

+5

L'attribut obsolète possède également un deuxième paramètre qui spécifie que l'utilisation de la propriété doit être considérée comme une erreur. À ce stade, vous devriez obtenir une erreur de compilation, ce qui est utile. –

+0

Merci Brian, j'aurais dû penser à le mentionner, bonne prise. – BenAlabaster

+0

Je crois que vous pouvez également utiliser le mot-clé "new" pour spécifier une nouvelle fonctionnalité pour la propriété même si elle n'est pas marquée virtuelle. Cela lui permettrait de marquer la propriété comme obsolète même si elle provient d'une classe où la propriété n'était pas virtuelle. –

1

Vous ne pouvez pas, c'est tout le point de l'héritage: la sous-classe doit offrir toutes les méthodes et propriétés de la classe de base.

Vous pouvez changer la mise en œuvre de lancer une exception lorsque la propriété est appelée (si elle était virtuelle) ...

25

Bien que techniquement la propriété ne sera pas caché, une façon de décourager fortement son utilisation est de mettre les attributs sur comme ceux-ci:

[Browsable(false)] 
[Bindable(false)] 
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] 
[EditorBrowsable(EditorBrowsableState.Never)] 

C'est ce que System.Windows.Forms fait pour les contrôles qui ont propriétés qui ne correspondent pas. La propriété Text, par exemple, est sur Contrôle, mais cela n'a aucun sens sur toutes les classes qui héritent de Control. Ainsi, dans MonthCalendar, par exemple, il apparaît comme celui-ci (par réflecteur):

[Browsable(false), Bindable(false), 
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), 
    EditorBrowsable(EditorBrowsableState.Never)] 
public override string Text 
{ 
    get { return base.Text; } 
    set { base.Text = value; } 
} 

Browsable indique si le membre apparaît dans la fenêtre Propriétés. EditorBrowsable indique s'il apparaît dans la liste déroulante Intellisense. Vous pouvez toujours taper la propriété si EditorBrowsable est défini sur false, et il sera toujours construit, mais il ne sera pas aussi évident que vous pouvez l'utiliser.

+1

Je devais écrire 'public new string Text' pour que cela fonctionne. – szamil

19

Il suffit de le cacher

public class ClassBase 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 
public class ClassA : ClassBase 
{ 
    public int JustNumber { get; set; } 
    private new string Name { get { return base.Name; } set { base.Name = value; } } 
    public ClassA() 
    { 
     this.ID = 0; 
     this.Name = string.Empty; 
     this.JustNumber = 0; 
    } 
} 

Note: Nom sera toujours membre du public de ClassBase, compte tenu de la contrainte de ne pas changer la classe de base il n'y a pas moyen d'arrêter cela.

+4

Avec cette méthode, faire quelque chose comme 'ClassA.Name' en dehors de' ClassA' exposera la propriété 'ClassBase.Name'. Il n'est pas possible de "nouveau" un membre à un accès plus restreint qu'il a été déclaré à l'origine. –

+0

C'est le point de la note. Le nom ne sera pas un membre public de ClassA selon la question. Pour la sérialisation et la réflexion, cela peut être important. – CCondron

+0

Oui, c'est vrai. Il est cependant intéressant de noter que lorsque l'on veut cacher un membre d'une classe héritée, cela peut être dû au fait qu'il définit la valeur du membre sur un ensemble de valeurs prédéfinies et ainsi le changer peut entraîner un comportement inattendu. –

4

Pourquoi forcer l'héritage quand ce n'est pas nécessaire? Je pense que la bonne façon de le faire est en faisant has-a au lieu d'un is-a.

public class ClassBase 
{ 
    public int ID { get; set; } 

    public string Name { get; set; } 
} 

public class ClassA 
{ 
    private ClassBase _base; 

    public int ID { get { return this._base.ID; } } 

    public string JustNumber { get; set; } 

    public ClassA() 
    { 
     this._base = new ClassBase(); 
     this._base.ID = 0; 
     this._base.Name = string.Empty; 
     this.JustNumber = string.Empty; 
    } 
} 
3

Je ne pense pas que beaucoup de personnes qui répondent ici comprennent l'héritage du tout. Il est nécessaire d'hériter d'une classe de base et de cacher ses variables et fonctions autrefois publiques. Exemple, disons que vous avez un moteur de base et que vous voulez faire un nouveau moteur qui est suralimenté. Eh bien, 99% du moteur que vous utiliserez, mais vous allez modifier un peu ses fonctionnalités pour le faire fonctionner beaucoup mieux et pourtant il y a encore certaines fonctionnalités qui devraient seulement être montrées aux modifications faites, pas l'utilisateur final. Parce que nous savons tous que toutes les classes de MS ne nécessitent aucune modification. En plus d'utiliser le nouveau pour remplacer simplement la fonctionnalité, c'est l'une des choses que Microsoft dans leur infinie wis ... oh, je veux dire des erreurs considérées comme un outil qui ne vaut plus la peine.

La meilleure façon d'accomplir cela maintenant est l'héritage à plusieurs niveaux.
public class ClassA {} public class B: A {} public class C: B {}

Classe B fait tout votre travail et la classe C expose ce que vous avez besoin exposé.

0

Je pense que c'est une mauvaise conception si vous devez le faire, surtout si vous êtes capable de concevoir le code à partir de zéro.

Pourquoi?

Une bonne conception consiste à laisser la classe de base partager les propriétés communes d'un certain concept (virtuel ou réel). Exemple: System.IO.Stream en C#. Plus bas sur la voie, une mauvaise conception augmentera le coût de maintenance et rendra la mise en œuvre de plus en plus difficile. Evitez cela autant que possible!

règles de base que j'utilise:

  • Réduire au minimum le nombre de propriétés et méthodes dans la classe de base. Si vous ne prévoyez pas d'utiliser certaines propriétés ou méthodes dans une classe qui hérite de la classe de base; ne le mettez pas dans le baseclass alors. Si vous êtes dans le développement d'un projet revenez toujours à la planche à dessin maintenant et vérifiez la conception parce que les choses changent! Refonte lorsque nécessaire. Lorsque votre projet est en ligne, les coûts de modification des choses plus tard dans la conception vont augmenter!

    • Si vous utilisez un baseclass mis en œuvre par un 3: tierce partie, considérer « monter » un niveau au lieu de « remplaçant » par « NotImplementedException » ou tel. S'il n'y a pas d'autre niveau, pensez à concevoir le code à partir de zéro.

    • Considérez toujours les classes de cachetage que vous ne voulez pas que quelqu'un puisse en hériter. Cela oblige les codeurs à "monter d'un niveau" dans la "hiérarchie d'héritage" et ainsi "des fins sans fin" comme "NotImplementedException" peuvent être évités.

0

Je sais que la question est vieux, mais ce que vous pouvez faire est de passer outre les PostFilterProperties comme celui-ci:

protected override void PostFilterProperties(System.Collections.IDictionary properties) 
    { 
     properties.Remove("AccessibleDescription"); 
     properties.Remove("AccessibleName"); 
     properties.Remove("AccessibleRole"); 
     properties.Remove("BackgroundImage"); 
     properties.Remove("BackgroundImageLayout"); 
     properties.Remove("BorderStyle"); 
     properties.Remove("Cursor"); 
     properties.Remove("RightToLeft"); 
     properties.Remove("UseWaitCursor"); 
     properties.Remove("AllowDrop"); 
     properties.Remove("AutoValidate"); 
     properties.Remove("ContextMenuStrip"); 
     properties.Remove("Enabled"); 
     properties.Remove("ImeMode"); 
     //properties.Remove("TabIndex"); // Don't remove this one or the designer will break 
     properties.Remove("TabStop"); 
     //properties.Remove("Visible"); 
     properties.Remove("ApplicationSettings"); 
     properties.Remove("DataBindings"); 
     properties.Remove("Tag"); 
     properties.Remove("GenerateMember"); 
     properties.Remove("Locked"); 
     //properties.Remove("Modifiers"); 
     properties.Remove("CausesValidation"); 
     properties.Remove("Anchor"); 
     properties.Remove("AutoSize"); 
     properties.Remove("AutoSizeMode"); 
     //properties.Remove("Location"); 
     properties.Remove("Dock"); 
     properties.Remove("Margin"); 
     properties.Remove("MaximumSize"); 
     properties.Remove("MinimumSize"); 
     properties.Remove("Padding"); 
     //properties.Remove("Size"); 
     properties.Remove("DockPadding"); 
     properties.Remove("AutoScrollMargin"); 
     properties.Remove("AutoScrollMinSize"); 
     properties.Remove("AutoScroll"); 
     properties.Remove("ForeColor"); 
     //properties.Remove("BackColor"); 
     properties.Remove("Text"); 
     //properties.Remove("Font"); 
    } 
+0

Je pense que ce code est spécifique aux winforms ou aux formulaires ASP ou Xamarin. Mentionnez-le dans votre réponse – Kira

2

Je suis entièrement d'accord que les propriétés ne doivent pas être retirées des classes de base, mais parfois une classe dérivée peut avoir une manière différente et plus appropriée d'entrer les valeurs. Dans mon cas, par exemple, j'hérite de ItemsControl. Comme nous le savons tous, ItemsControl a la propriété ItemsSource, mais je veux que mon contrôle fusionne les données de 2 sources (par exemple, Personne et Emplacement). Si je devais demander à l'utilisateur d'entrer les données à l'aide d'ItemsSource, je devrais séparer puis recombiner les valeurs, j'ai donc créé 2 propriétés pour entrer les données. Mais revenons à la question d'origine, cela laisse le ItemsSource, que je ne veux pas que l'utilisateur utilise parce que je le "remplace" avec mes propres propriétés. J'aime les idées Browsable et EditorBrowsable, mais cela n'empêche pas l'utilisateur de l'utiliser. Le point de base ici est que l'héritage devrait garder la plupart des propriétés, mais quand il y a une grande classe complexe (en particulier celles où vous ne pouvez pas modifier le code original), réécrire tout serait très inefficace.

Questions connexes