2009-12-09 25 views
33

se ho il seguente esempio di codice:Come nascondere una proprietà ereditata in una classe senza modificare la classe ereditata (classe 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; 
    } 
} 

Cosa devo fare per nascondere la proprietà Name (Non mostrato come membro dei membri ClassA) senza modificare ClassBase?

+1

Questa è una violazione di uno dei principi di base della programmazione orientata agli oggetti (il polimorfismo e, relazione a ciò, il 'L' in solido). – jason

+1

In particolare una violazione del principio di sostituzione di Liskov (http://en.wikipedia.org/wiki/Liskov_substitution_principle) –

+13

Molte delle risposte ignorare il semplice fatto che una classe base può essere fornito da una terza parte. Poiché questo codice non è di proprietà, non può essere modificato. C'è una certa validità nel voler creare una versione più focalizzata della classe base nascondendo i membri. Ed è preferibile ereditare piuttosto che usare il controllo di base come componente di un nuovo controllo in quanto richiede un sacco di lavoro extra nell'esporre i metodi già trovati nella base. – Mario

risposta

46

I odore un odore di codice qui. È mia opinione che dovresti ereditare solo una classe base se stai implementando tutte le funzionalità di quella classe base. Quello che stai facendo non rappresenta in modo appropriato i principi orientati agli oggetti. Quindi, se vuoi ereditare dalla tua base, dovresti implementare il Nome, altrimenti hai la tua eredità nel modo sbagliato. La tua classe A dovrebbe essere la tua classe base e la tua attuale classe base dovrebbe ereditare da A se è quello che vuoi, non viceversa.

Tuttavia, non allontanarsi troppo dalla domanda diretta. Se fatto vuole a non rispettare "le regole" e vuole continuare sulla strada che avete scelto - ecco come si può andare a questo proposito:

La convenzione è quello di implementare la proprietà, ma gettare un NotImplementedException quando la proprietà si chiama - anche se non mi piace neanche quello. Ma questa è la mia opinione personale e non cambia il fatto che questa convenzione sia ancora valida.

Se si sta tentando di obsoleta la proprietà (ed è dichiarato nella classe di base come virtuale), allora si potrebbe usare sia l'attributo obsoleto su di esso:

[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: Come Brian sottolineato nei commenti, il secondo parametro dell'attributo causerà un errore del compilatore se qualcuno fa riferimento alla proprietà Name, quindi non sarà in grado di utilizzarlo anche se lo hai implementato nella classe derivata.

Oppure, come ho detto, usa NotImplementedException:

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

Tuttavia, se la proprietà non è dichiarata come virtuale, quindi è possibile utilizzare la nuova parola chiave per sostituirlo:

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

È comunque possibile utilizzare l'attributo obsoleto nello stesso modo come se il metodo è stato sovrascritto oppure puoi lanciare NotImplementedException, a seconda di quale si sceglie. Io probabilmente usare:

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

o:

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

A seconda se o non è stata dichiarata come virtuale nella classe base.

+5

L'attributo obsoleto ha anche un secondo parametro che specifica che l'utilizzo della proprietà deve essere considerato un errore. A quel punto dovresti ottenere un errore in fase di compilazione, che è utile. –

+0

Grazie Brian, avrei pensato di dirlo, buona cattura. – BenAlabaster

+0

Credo che si possa usare anche la "nuova" parola chiave per specificare nuove funzionalità per la proprietà anche se non è contrassegnata come virtuale. Ciò gli consentirebbe di contrassegnare la proprietà come obsoleta, anche se proviene da una classe in cui la proprietà non era virtuale. –

1

Non è possibile, questo è l'intero punto dell'ereditarietà: la sottoclasse deve offrire tutti i metodi e le proprietà della classe base.

si potrebbe cambiare l'implementazione un'eccezione quando la proprietà viene chiamata (se fosse virtuale) ...

25

Anche se tecnicamente la proprietà non sarà nascosto, un modo per scoraggiare fortemente il suo uso è quello di mettere gli attributi su di esso come questi:

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

Questo è ciò che fa System.Windows.Forms per i controlli che hanno proprietà che non si adattano. La proprietà Text, ad esempio, è su Control, ma non ha senso su ogni classe che eredita da Control. Quindi, in MonthCalendar, per esempio, appare come questa (per Reflector):

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

sfogliabile indica se il membro si presenta nella finestra Proprietà. EditorBrowsable indica se viene visualizzato nel menu a discesa Intellisense. Puoi ancora scrivere la proprietà se EditorBrowsable è impostato su false, e continuerà a essere compilato, ma non sarà così ovvio che puoi usarlo.

+1

Ho dovuto scrivere 'public new string Text' per farlo funzionare. – szamil

19

Basta nasconderlo

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; 
    } 
} 

Nota: Nome sarà ancora un membro pubblico di ClassBase, dato il vincolo di non cambiare la classe base non c'è modo di fermare questo.

+4

Con questo metodo, fare qualcosa come 'ClassA.Name' al di fuori di' ClassA' esporrà la proprietà 'ClassBase.Name'. Non è possibile "nuovo" un membro per un accesso più limitato di quanto originariamente dichiarato. –

+0

Questo è il punto della nota. Il nome non sarà un membro pubblico di ClassA come da domanda. Per la serializzazione e la riflessione questo può essere importante. – CCondron

+0

Sì, è vero. È comunque degno di nota che quando si vuole nascondere un membro da una classe ereditata, può essere perché sta impostando il valore del membro su uno di un insieme di valori predefiniti e quindi modificarlo può comportare un comportamento imprevisto. –

4

Perché forzare l'ereditarietà quando non è necessaria? Penso che il modo corretto di farlo è fare ha un invece di uno è un.

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

non credo che un sacco di persone che hanno risposto qui capire eredità a tutti. È necessario ereditare da una classe base e nascondere le sue var e funzioni una volta pubbliche. Esempio, diciamo che hai un motore di base e vuoi creare un nuovo motore che sia sovralimentato. Bene, il 99% del motore verrà utilizzato ma si modificheranno un po 'delle sue funzionalità per farlo funzionare molto meglio e tuttavia ci sono alcune funzionalità che dovrebbero essere mostrate solo alle modifiche apportate, non all'utente finale. Perché sappiamo tutti che ogni classe MS mette fuori non ha davvero bisogno di alcuna modifica.

Oltre a utilizzare il nuovo per sovrascrivere semplicemente la funzionalità, è una delle cose che Microsoft ha nei suoi infiniti saggi ... .. oh, voglio dire che gli errori considerati uno strumento non valgono più la pena.

Il modo migliore per ottenere questo risultato è l'ereditarietà multi-livello.
public class classA {} public class B: A {} pubblica Classe C: B {}

Classe B fa tutto il vostro lavoro e la classe C espone quello che ti serve esposto.

0

Penso che sia un cattivo progetto se si deve fare questo, soprattutto se si è in grado di progettare il codice da zero.

Perché?

Il buon design è di consentire alla classe base di condividere le proprietà comuni che un determinato concetto ha (virtuale o reale). Esempio: System.IO.Stream in C#.

Più avanti lungo la corsia, la progettazione errata aumenterà i costi per la manutenzione e renderà l'implementazione sempre più difficile. Evita questo il più possibile!

regole di base che uso:

  • minimizzare il numero di proprietà e metodi della classe base. Se non si prevede di utilizzare alcune proprietà o metodi in una classe che eredita la classe base; non metterlo nella baseclass allora. Se sei nello stadio di sviluppo di un progetto; torna sempre al tavolo da disegno ora e poi controlla il design perché le cose cambiano! Ridisegnare quando necessario. Quando il tuo progetto è in diretta, i costi per cambiare le cose in seguito nel design saliranno!

    • Se si utilizza un baseclass implementato da un 3: partito terzo, considerare "salire" di un livello invece di "override" con "NotImplementedException" o simili. Se non ci sono altri livelli, considera di progettare il codice da zero.

    • Sempre in considerazione per sigillare le classi che non si desidera a chiunque di essere in grado di ereditare esso. Costringe i programmatori a "salire di un livello" nella "gerarchia dell'ereditarietà" e quindi "le estremità libere" come "NotImplementedException" possono essere evitate.

0

So che la questione è vecchio, ma che cosa si può fare è ignorare i PostFilterProperties come questo:

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

Penso che questo codice sia specifico per Winforms o ASP forse per i moduli Xamarin. Menzionalo nella tua risposta – Kira

2

Sono completamente d'accordo che le proprietà non dovrebbero essere rimossi da classi base, ma a volte una classe derivata potrebbe avere un modo diverso più appropriato per inserire i valori. Nel mio caso, ad esempio, sto ereditando da ItemsControl. Come tutti sappiamo, ItemsControl ha la proprietà ItemsSource, ma voglio che il mio controllo unisca i dati da 2 fonti (ad esempio, Person e Location). Se dovessi inserire l'utente nei dati utilizzando ItemsSource, avrei bisogno di separare e ricombinare i valori, quindi ho creato 2 proprietà per inserire i dati. Ma tornando alla domanda originale, questo lascia la risorsa ItemsSource, che non voglio che l'utente usi perché lo sto "sostituendo" con le mie proprietà. Mi piacciono le idee Browsable e EditorBrowsable, ma ancora non impedisce all'utente di usarlo. Il punto fondamentale è che l'ereditarietà dovrebbe mantenere la maggioranza delle proprietà, ma quando c'è un grande complesso di classe (soprattutto quelli in cui non è possibile modificare il codice originale), riscrivendo tutto sarebbe molto inefficiente.

Problemi correlati