Dovresti mai usare variabili membro protette?


97

Dovresti mai usare variabili membro protette? Quali sono i vantaggi e quali problemi può causare?

Risposte:


74

Dovresti mai usare variabili membro protette?

Dipende da quanto sei esigente nel nascondere lo stato.

  • Se non si desidera alcuna perdita di stato interno, dichiarare private tutte le variabili membro è la strada da percorrere.
  • Se non ti interessa davvero che le sottoclassi possano accedere allo stato interno, allora protected è abbastanza buono.

Se arriva uno sviluppatore e sottoclasse la tua classe, potrebbe rovinarla perché non la capisce completamente. Con i membri privati, oltre all'interfaccia pubblica, non possono vedere i dettagli specifici dell'implementazione di come vengono fatte le cose, il che ti dà la flessibilità di cambiarlo in seguito.


1
Puoi commentare le prestazioni delle variabili protette rispetto a una variabile privata con un metodo get / set?
Jake

3
Direi che non è qualcosa di cui vale la pena preoccuparsi a meno che non si scopra attraverso la profilazione che il collo di bottiglia finisce per essere gli accessori (cosa che non è quasi mai). Ci sono trucchi che possono essere fatti per rendere la JIT più intelligente sulle cose se finisce per essere un problema. In java, ad esempio, puoi suggerire che la funzione di accesso può essere inline contrassegnandola come finale. Sebbene onestamente, le prestazioni di getter e setter sono di gran lunga meno importanti rispetto all'organizzazione del sistema e ai problemi di prestazioni effettivi determinati da un profiler.
Allain Lalonde,

23
@ Jake: Non dovresti mai prendere decisioni di progettazione basate su ipotesi di prestazione. Prendi decisioni di progettazione in base a ciò che ritieni sia il miglior design e solo se la tua profilazione nella vita reale mostra un collo di bottiglia nel tuo progetto, vai e risolvilo. Di solito se il design è valido, anche le prestazioni sono buone.
Mecki

Con i membri privati, oltre all'interfaccia pubblica, non possono vedere i dettagli specifici dell'implementazione Possono semplicemente aprire la classe e cercarla, quindi non ha senso ?!
Nero

2
@Black Chiaramente Allain significava "non possono accedere " a quei membri e quindi non possono costruire codice contro di loro, lasciando l'autore della classe libero di rimuovere / modificare i membri protetti in seguito. (Ovviamente, l'idioma pimpl consentirebbe di nasconderli visivamente e anche dalle unità di traduzione inclusa l'intestazione.)
underscore_d

31

La sensazione generale al giorno d'oggi è che causino un accoppiamento indebito tra classi derivate e le loro basi.

Non hanno alcun vantaggio particolare rispetto a metodi / proprietà protetti (una volta potevano avere un leggero vantaggio in termini di prestazioni), e sono stati anche utilizzati maggiormente in un'epoca in cui l'ereditarietà molto profonda era di moda, cosa che al momento non è.


2
Non dovrebbe no particular advantage over protected methods/propertiesessere no particular advantage over *private* methods/properties?
Penghe Geng

No, perché sto / stavo parlando dei vantaggi / svantaggi di vari modi di comunicazione tra classi derivate e le loro basi - tutte queste tecniche sarebbero 'protette' - la differenza è se sono variabili membro (campi) o proprietà / metodi ( cioè subroutine di qualche tipo).
Will Dean

1
Grazie per il rapido chiarimento. Sono lieto di avere la risposta del poster originale in un'ora per la mia domanda a un post di 6 anni. Non pensi che possa accadere nella maggior parte degli altri forum online :)
Penghe Geng

9
Ancora più notevole è che in realtà sono d'accordo con me stesso per tutto quel tempo ...
Will Dean,

Un ordine del lavoro di un costruttore è fare in modo che tutte le variabili di stato siano esplicitamente inizializzate. Se aderisci a questa convenzione, puoi utilizzare il supercostrutto per chiamare il costruttore genitore; si occuperà quindi di inizializzare le variabili di stato privato nella classe genitore.
ncmathsadist

31

In genere, se qualcosa non è deliberatamente concepito come pubblico, lo rendo privato.

Se si verifica una situazione in cui è necessario accedere a quella variabile o metodo privato da una classe derivata, lo cambio da privato a protetto.

Questo non accade quasi mai: non sono affatto un fan dell'ereditarietà, poiché non è un modo particolarmente buono per modellare la maggior parte delle situazioni. In ogni caso, vai avanti, niente paura.

Direi che va bene (e probabilmente il modo migliore per farlo) per la maggior parte degli sviluppatori.

Il semplice fatto è che , se qualche altro sviluppatore arriva un anno dopo e decide di aver bisogno di accedere alla tua variabile membro privato, modificherà semplicemente il codice, lo cambierà in protetto e continuerà con la propria attività.

Le uniche vere eccezioni a questo sono se sei nel business della spedizione di DLL binarie in forma di scatola nera a terze parti. Questo è costituito fondamentalmente da Microsoft, quei fornitori di "Custom DataGrid Control" e forse alcune altre grandi app fornite con librerie di estensibilità. A meno che tu non sia in quella categoria, non vale la pena dedicare tempo / fatica a preoccuparsi di questo genere di cose.


8

Il problema chiave per me è che una volta che si crea una variabile protetta, non è possibile consentire a nessun metodo della classe di fare affidamento sul suo valore all'interno di un intervallo, perché una sottoclasse può sempre posizionarlo fuori intervallo.

Ad esempio, se ho una classe che definisce la larghezza e l'altezza di un oggetto renderizzabile e rendo protette quelle variabili, non posso fare supposizioni (ad esempio) sulle proporzioni.

Criticamente, non posso mai fare queste supposizioni in nessun momento dal momento in cui il codice viene rilasciato come libreria, poiché anche se aggiorno i miei setter per mantenere le proporzioni, non ho alcuna garanzia che le variabili vengano impostate tramite i setter o accessibili tramite il getter nel codice esistente.

Né nessuna sottoclasse della mia classe può scegliere di fare quella garanzia, in quanto non può nemmeno applicare i valori delle variabili, anche se questo è l'intero punto della loro sottoclasse .

Come esempio:

  • Ho una classe rettangolo con larghezza e altezza memorizzate come variabili protette.
  • Un'ovvia sottoclasse (nel mio contesto) è una classe "DisplayedRectangle", dove l'unica differenza è che restringo le larghezze e le altezze a valori validi per una visualizzazione grafica.
  • Ma ora è impossibile , poiché la mia classe DisplayedRectangle non può veramente vincolare quei valori, poiché qualsiasi sottoclasse potrebbe sovrascrivere i valori direttamente, pur essendo trattata come un DisplayedRectangle.

Vincolando le variabili a essere private, posso quindi applicare il comportamento che desidero tramite setter o getter.


7

In generale, terrei le tue variabili membro protette nel raro caso in cui hai il controllo totale anche sul codice che le utilizza. Se stai creando un'API pubblica, direi mai. Di seguito, faremo riferimento alla variabile membro come una "proprietà" dell'oggetto.

Ecco cosa non può fare la tua superclasse dopo aver protetto una variabile membro anziché privata con funzioni di accesso:

  1. creare pigramente un valore al volo quando la proprietà viene letta. Se aggiungi un metodo getter protetto, puoi creare pigramente il valore e restituirlo.

  2. sapere quando la proprietà è stata modificata o eliminata. Questo può introdurre bug quando la superclasse fa ipotesi sullo stato di quella variabile. La creazione di un metodo setter protetto per la variabile mantiene quel controllo.

  3. Impostare un punto di interruzione o aggiungere un output di debug quando la variabile viene letta o scritta.

  4. Rinomina quella variabile membro senza cercare in tutto il codice che potrebbe utilizzarla.

In generale, penso che sarebbe il raro caso in cui consiglierei di creare una variabile membro protetta. È meglio passare qualche minuto a esporre la proprietà tramite getter / setter che ore dopo rintracciare un bug in qualche altro codice che ha modificato la variabile protetta. Non solo, ma sei assicurato contro l'aggiunta di funzionalità future (come il caricamento lento) senza interrompere il codice dipendente. È più difficile farlo più tardi che farlo ora.


7

A livello di progettazione potrebbe essere appropriato utilizzare una proprietà protetta, ma per l'implementazione non vedo alcun vantaggio nel mappare questo a una variabile membro protetta piuttosto che ai metodi accessor / mutator.

Le variabili membro protette presentano svantaggi significativi perché consentono effettivamente al codice client (la sottoclasse) di accedere allo stato interno della classe di base. Ciò impedisce alla classe base di mantenere efficacemente le sue invarianti.

Per lo stesso motivo, le variabili membro protette rendono anche la scrittura di codice multi-thread sicuro molto più difficile a meno che non sia garantita costante o limitata a un singolo thread.

I metodi accessorio / mutatore offrono una stabilità dell'API notevolmente maggiore e flessibilità di implementazione durante la manutenzione.

Inoltre, se sei un purista OO, gli oggetti collaborano / comunicano inviando messaggi, non leggendo / impostando lo stato.

In cambio offrono pochissimi vantaggi. Non li rimuoverei necessariamente dal codice di qualcun altro, ma non li uso io stesso.


4

Il più delle volte, è pericoloso usare protected perché rompi in qualche modo l'incapsulamento della tua classe, che potrebbe essere scomposta da una classe derivata mal progettata.

Ma ho un buon esempio: diciamo che puoi usare una sorta di contenitore generico. Ha un'implementazione interna e funzioni di accesso interne. Ma devi offrire almeno 3 accessi pubblici ai suoi dati: map, hash_map, vector-like. Quindi hai qualcosa come:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

Ho usato questo tipo di codice meno di un mese fa (quindi il codice è dalla memoria). Dopo un po 'di riflessione, credo che mentre il contenitore Base generico dovrebbe essere una classe astratta, anche se può vivere abbastanza bene, perché usare direttamente Base sarebbe un tale problema dovrebbe essere vietato.

Riepilogo In questo modo sono stati protetti i dati utilizzati dalla classe derivata. Tuttavia, dobbiamo tenere conto del fatto che la classe Base dovrebbe essere astratta.


non rompe l'incapsulamento più di quanto facciano i membri pubblici. È un'impostazione per dire che le classi derivate possono utilizzare lo stato della classe che non è esposto agli utenti della classe.
gbjbaanb

@gbjbaanb: Ti stai contraddicendo "non interrompe l'incapsulamento più di quanto fanno i membri pubblici" è diverso da "[solo] le classi derivate possono usare lo stato della classe". "protetto" è il mezzo tra pubblico e privato. Quindi "protetto [...] rompe un po 'l'incapsulamento" è ancora vero ...
paercebal

in realtà, nel linguaggio c ++, adattatori del contenitore come std :: stack esporranno l'oggetto contenitore sottostante con una variabile protetta chiamata "c".
Johannes Schaub - litb

So che questo post è piuttosto vecchio, ma sento il bisogno di intervenire. Non rompi "un po '" l'incapsulamento, lo rompi completamente. protectednon è più incapsulato di public. Sono disposto a essere smentito. Tutto quello che devi fare è scrivere una classe con un membro protetto e proibirmi di modificarlo. Ovviamente la classe deve essere non finale, poiché l'intero scopo dell'utilizzo di protected è per l'ereditarietà. O qualcosa è incapsulato o non lo è. Non esiste uno stato intermedio.
Taekahn

3

In breve, sì.

Le variabili membro protette consentono l'accesso alla variabile da qualsiasi sottoclasse e da qualsiasi classe nello stesso pacchetto. Questo può essere molto utile, soprattutto per i dati di sola lettura. Non credo che siano mai necessari, tuttavia, perché qualsiasi utilizzo di una variabile membro protetta può essere replicato utilizzando una variabile membro privata e un paio di getter e setter.


1
Al contrario, anche le variabili membro private non sono mai necessarie; public è sufficiente per qualsiasi utilizzo.
Alice

3

Solo per la cronaca, sotto la voce 24 di "C ++ eccezionale", in una delle note a piè di pagina, Sutter dice "non scriveresti mai una classe che ha una variabile membro pubblica o protetta. Giusto? (Indipendentemente dallo scarso esempio fornito da alcune biblioteche .) "


2

Per informazioni dettagliate sui modificatori di accesso .Net vai qui

Non ci sono vantaggi o svantaggi reali per le variabili membro protette, è una questione di ciò di cui hai bisogno nella tua situazione specifica. In generale è pratica accettata dichiarare le variabili membro come private e abilitare l'accesso esterno tramite le proprietà. Inoltre, alcuni strumenti (ad esempio alcuni mappatori O / R) si aspettano che i dati degli oggetti siano rappresentati da proprietà e non riconoscono le variabili membro pubbliche o protette. Ma se sai che vuoi che le tue sottoclassi (e SOLO le tue sottoclassi) accedano a una certa variabile, non c'è motivo per non dichiararla protetta.


Volendo sottoclassi di accedere una variabile è molto diverso dal voler loro di essere in grado di liberamente mutare esso. Questo è uno dei principali argomenti contro le variabili protette: ora la tua classe base non può assumere che nessuno dei suoi invarianti valga, perché qualsiasi classe derivata può fare assolutamente qualsiasi cosa con i membri protetti. Questo è l'argomento principale contro di loro. Se hanno solo bisogno di accedere ai dati, allora ... scrivi una funzione di accesso. : P (Uso variabili protette, anche se probabilmente più di quanto dovrei, e cercherò di ridurre!)
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.