Domanda

Utilizzo raramente l'ereditarietà, ma quando lo faccio non utilizzo mai gli attributi protetti perché penso che rompa l'incapsulamento delle classi ereditate.

Utilizzi attributi protetti?per cosa li usi?

È stato utile?

Soluzione

In questo colloquio su disegno di Bill Venners, Joshua Bloch, l'autore di Java efficace dice:

Affidarsi alle sottoclassi

Bill Venner: Dovrei fidarmi delle sottoclassi più intimamente dei non subclasse?Ad esempio, renderò più facile per un'implementazione della sottoclasse di spezzarmi di quanto farei per una non subclasse?In particolare, come ti senti riguardo ai dati protetti?

Josh Bloch: Scrivere qualcosa che sia sottoclassibile e robusto contro una sottoclasse dannosa è in realtà una cosa piuttosto difficile da fare, supponendo che tu dia l'accesso alla sottoclasse alle tue strutture di dati interne.Se la sottoclasse non ha accesso a nulla che un utente normale non fa, è più difficile per la sottoclasse fare danni.Ma a meno che tu non creda tutti i tuoi metodi finali, la sottoclasse può comunque rompere i tuoi contratti semplicemente facendo le cose sbagliate in risposta all'invocazione del metodo.Questo è esattamente il motivo per cui le classi critiche di sicurezza come String sono definitive.Altrimenti qualcuno potrebbe scrivere una sottoclasse che fa sembrare le stringhe mutabili, il che sarebbe sufficiente per rompere la sicurezza.Quindi devi fidarti delle tue sottoclassi.Se non ti fidi di loro, non puoi permetterlo, perché le sottoclassi possono così facilmente far violare una lezione.

Per quanto riguarda i dati protetti in generale, è un male necessario.Dovrebbe essere ridotto al minimo.I dati e i metodi protetti più protetti equivalgono a impegnarsi in un dettaglio di implementazione.Un campo protetto è un dettaglio di implementazione che stai rendendo visibile alle sottoclassi.Anche un metodo protetto è un pezzo di struttura interna che si sta rendendo visibile alle sottoclassi.

Il motivo per cui lo rendi visibile è che è spesso necessario per consentire alle sottoclassi di svolgere il proprio lavoro o di farlo in modo efficiente.Ma una volta fatto, ti impegni.Ora è qualcosa che non ti è permesso cambiare, anche se in seguito trovi un'implementazione più efficiente che non comporta più l'uso di un particolare campo o metodo.

Quindi tutte le altre cose sono uguali, non dovresti avere membri protetti.Detto questo, se hai troppo pochi, allora la tua classe potrebbe non essere utilizzabile come super classe, o almeno non come una super classe efficiente.Spesso lo scopri dopo il fatto.La mia filosofia è quella di avere il minor numero possibile di membri protetti quando scrivi per la prima volta la classe.Quindi prova a sottoclassarlo.Potresti scoprire che senza un particolare metodo protetto, tutte le sottoclassi dovranno fare una brutta cosa.

Ad esempio, se guardi AbstractList, scoprirai che esiste un metodo protetto per eliminare un intervallo dell'elenco in un colpo (removeRange).Perché è lì?Perché il normale linguaggio per rimuovere un intervallo, basato sull'API pubblica, è chiamare subList per ottenere un sub-List, e poi chiama clear su quel sotto-List.Senza questo particolare metodo protetto, tuttavia, l'unica cosa che clear potrebbe fare è rimuovere ripetutamente i singoli elementi.

Pensaci.Se hai una rappresentazione di array, cosa farà?Crollerà ripetutamente l'array, facendo ordine e lavoro e volte.Quindi ci vorrà una quantità quadratica di lavoro, invece della quantità lineare di lavoro che dovrebbe.Fornendo questo metodo protetto, consentiamo a qualsiasi implementazione in grado di eliminare in modo efficiente un intero intervallo.E qualsiasi ragionevole List L'implementazione può eliminare un intervallo in modo più efficiente tutto in una volta.

Che avremmo bisogno di questo metodo protetto è qualcosa che dovresti essere molto più intelligente di me per sapere in anticipo.Fondamentalmente, ho implementato la cosa.Quindi, quando abbiamo iniziato a sottoclassare, ci siamo resi conto che la cancellazione era quadratica.Non potevamo permettermelo, quindi ho inserito il metodo protetto.Penso che sia l'approccio migliore con metodi protetti.Inserisci il minor numero possibile, quindi aggiungi altro, necessariamente.I metodi protetti rappresentano impegni nei progetti che potresti voler cambiare.Puoi sempre aggiungere metodi protetti, ma non puoi eliminarli.

Bill Venner: E i dati protetti?

Josh Bloch: La stessa cosa, ma anche di più.I dati protetti sono ancora più pericolosi in termini di incastamento dei dati invarianti.Se dai a qualcun altro l'accesso ad alcuni dati interni, hanno un regno libero su di esso.

Versione breve:rompe l'incapsulamento ma è un male necessario che dovrebbe essere ridotto al minimo.

Altri suggerimenti

C#:

Utilizzo protetto per metodi astratti o virtuali che desidero che le classi base sovrascrivano.Rendo anche protetto un metodo se può essere chiamato dalle classi base, ma non voglio che venga chiamato al di fuori della gerarchia delle classi.

Potresti averne bisogno per gli attributi statici (o "globali") di cui desideri che le tue sottoclassi o classi dello stesso pacchetto (se si tratta di Java) traggano beneficio.

Quegli attributi finali statici che rappresentano una sorta di "valore costante" hanno raramente una funzione getter, quindi un attributo finale statico protetto potrebbe avere senso in quel caso.

Scott Meyers dice non usa gli attributi protetti in Effective C++ (3a ed.):

Articolo 22:Dichiarare privati ​​i membri dei dati.

Il motivo è lo stesso che dici tu:rompe gli incapsulamenti.La conseguenza è che altrimenti le modifiche locali al layout della classe potrebbero interrompere i tipi dipendenti e provocare modifiche in molti altri punti.

Non ci sono mai buone ragioni per avere attributi protetti.Una classe base deve poter dipendere dallo stato, il che significa limitare l'accesso ai dati tramite metodi di accesso.Non puoi dare a nessuno l'accesso ai tuoi dati privati, nemmeno ai bambini.

IL protected parola chiave è un errore concettuale e un pasticcio nella progettazione del linguaggio, e diversi linguaggi moderni, come Nim e Ceylon (vedi http://ceylon-lang.org/documentation/faq/lingual-design/#no_protected_modifier), che sono stati progettati attentamente anziché limitarsi a copiare errori comuni, non hanno tale parola chiave.

Non sono i membri protetti che interrompono l'incapsulamento, è l'esposizione dei membri che non dovrebbero essere esposti che interrompe l'incapsulamento...non importa se sono protetti o pubblici.Il problema con protected è che è sbagliato e fuorviante...membri dichiaranti protected (piuttosto che private) non li protegge, fa il contrario, esattamente come public fa.Un membro protetto, essendo accessibile all'esterno della classe, è esposto al mondo e quindi la sua semantica deve essere mantenuta per sempre, proprio come avviene per public.L'intera idea di "protetto" è una sciocchezza...l'incapsulamento non è sicurezza e la parola chiave non fa altro che favorire la confusione tra i due.Puoi aiutare un po' evitando tutti gli usi di protected nelle tue classi - se qualcosa è una parte interna dell'implementazione, non fa parte della semantica della classe e potrebbe cambiare in futuro, rendilo privato o interno al tuo pacchetto, modulo, assembly, ecc.Se è una parte immutabile della semantica della classe, rendila pubblica e non infastidirai gli utenti della tua classe che possono vedere che c'è un membro utile nella documentazione ma non possono usarlo, a meno che non stiano creando il loro proprie istanze e puoi ottenerlo tramite sottoclassi.

Non utilizzo gli attributi protetti in Java perché lì sono protetti solo dai pacchetti.Ma in C++ li userò in classi astratte, consentendo alla classe ereditaria di ereditarli direttamente.

In generale, no, non vuoi davvero utilizzare membri con dati protetti.Questo è doppiamente vero se stai scrivendo un'API.Una volta che qualcuno eredita dalla tua classe, non puoi mai veramente fare manutenzione e non romperlo in qualche modo in un modo strano e talvolta selvaggio.

Penso che gli attributi protetti siano una cattiva idea.Io uso Controlla Stile per far rispettare quella regola con i miei team di sviluppo Java.

Recentemente ho lavorato a un progetto in cui il membro "protetto" è stata un'ottima idea.La gerarchia delle classi era qualcosa del tipo:

[+] Base
 |
 +--[+] BaseMap
 |   |
 |   +--[+] Map
 |   |
 |   +--[+] HashMap
 |
 +--[+] // something else ?

La Base ha implementato un std::list ma nient'altro.All'utente era vietato l'accesso diretto alla lista, ma poiché la classe Base era incompleta, si affidava comunque a classi derivate per implementare il rinvio indiretto alla lista.

L'indiretto potrebbe provenire da almeno due sapori:std::map e stdext::hash_map.Entrambe le mappe si comporteranno allo stesso modo, ma per il fatto che hash_map necessita che la chiave sia hashable (in VC2003, lanciabile su size_t).

Quindi BaseMap ha implementato una TMap come tipo basato su modello che era un contenitore simile a una mappa.

Map e HashMap erano due classi derivate di BaseMap, una specializzata BaseMap su std::map e l'altra su stdext::hash_map.

COSÌ:

  • Base non era utilizzabile come tale (nessun accessor pubblico!) e forniva solo funzionalità e codice comuni

  • BaseMap necessitava di una facile lettura/scrittura su un std::list

  • Map e HashMap necessitavano di un facile accesso in lettura/scrittura alla TMap definita in BaseMap.

Per me, l'unica soluzione era utilizzare protected per std::list e le variabili membro TMap.Non avrei potuto metterli "privati" perché avrei comunque esposto tutte o quasi tutte le loro funzionalità tramite accessori di lettura/scrittura.

Alla fine, immagino che se finisci per dividere la tua classe in più oggetti, ogni derivazione aggiunge le funzionalità necessarie alla sua classe madre e solo la classe più derivata è realmente utilizzabile, allora proteggere è la strada da percorrere.Il fatto che il "membro protetto" fosse una classe, e quindi fosse quasi impossibile da "rompere", aiutava.

Altrimenti, la protezione dovrebbe essere evitata il più possibile (ad esempio:Utilizza private per impostazione predefinita e public quando devi esporre il metodo).

Li uso.In breve, è un buon modo se vuoi condividere alcuni attributi.Certo, potresti scrivere funzioni set/get per loro, ma se non c'è convalida, qual è il punto?È anche più veloce.

Considera questo:hai una classe che è la tua classe base.Ha alcuni attributi che non vuoi usare negli oggetti figlio.Potresti scrivere una funzione get/set per ciascuno oppure puoi semplicemente impostarli.

Il mio esempio tipico è un gestore di file/stream.Vuoi accedere al gestore (ad es.descrittore di file), ma vuoi nasconderlo dalle altre classi.È molto più semplice che scrivere una funzione set/get per questo.

In generale, sì.Un metodo protetto è solitamente migliore.

Nell'uso c'è un livello di semplicità dato dall'uso di protected finale variabile per un oggetto condiviso da tutti i figli di una classe.Sconsiglio sempre di utilizzarlo con primitive o raccolte poiché i contratti sono impossibili da definire per questi tipi.

Ultimamente sono arrivato a separare le cose che fai con le primitive e le raccolte grezze da cose che fai con classi ben formate.Le primitive e le raccolte dovrebbero SEMPRE essere private.

Inoltre, ho iniziato occasionalmente a esporre le variabili dei membri pubblici quando sono dichiarate definitive e sono classi ben formate che non sono troppo flessibili (di nuovo, non primitive o raccolte).

Questa non è una stupida scorciatoia, ci ho pensato molto seriamente e ho deciso che non c'è assolutamente alcuna differenza tra il pubblico finale variabile che espone un oggetto e un getter.

Dipende da cosa vuoi.Se desideri una lezione veloce, i dati dovrebbero essere protetti e utilizzare metodi protetti e pubblici.Perché penso che dovresti presumere che i tuoi utenti che derivano dalla tua classe conoscano abbastanza bene la tua classe o almeno abbiano letto il tuo manuale sulla funzione che sovrascriveranno.

Se i tuoi utenti creano problemi con la tua classe, non è un tuo problema.Ogni utente malintenzionato può aggiungere le seguenti righe durante l'override di uno dei tuoi virtuali:

(C#)

static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...

Non puoi sigillare ogni classe per impedirlo.

Ovviamente se vuoi un vincolo su alcuni dei tuoi campi, usa le funzioni o le proprietà di accesso o qualcosa che desideri e rendi quel campo privato perché non c'è altra soluzione...

Ma personalmente non mi piace attenermi a tutti i costi ai principi dell'oop.Soprattutto creando proprietà con l'unico scopo di rendere privati ​​i membri dei dati.

(C#):

private _foo;
public foo
{
   get {return _foo;}
   set {_foo=value;}
}

Questa era la mia opinione personale.

Ma fai ciò che richiede il tuo capo (se vuole campi privati, fallo).

Utilizzo variabili/attributi protetti all'interno delle classi base che so di non voler trasformare in metodi.In questo modo, le sottoclassi hanno pieno accesso alle variabili ereditate e non hanno il sovraccarico (creato artificialmente) di passare attraverso getter/setter per accedervi.Un esempio è una classe che utilizza un flusso I/O sottostante;ci sono pochi motivi per non consentire alle sottoclassi l'accesso diretto al flusso sottostante.

Questo va bene per le variabili membro che vengono utilizzate in modo semplice e diretto all'interno della classe base e di tutte le sottoclassi.Ma per una variabile che ha un uso più complicato (ad esempio, accedervi provoca effetti collaterali in altri membri della classe), una variabile accessibile direttamente non è appropriata.In questo caso, è possibile renderlo privato e al suo posto è possibile fornire getter/setter pubblici/protetti.Un esempio è un meccanismo di buffering interno fornito dalla classe base, in cui l'accesso ai buffer direttamente da una sottoclasse comprometterebbe l'integrità degli algoritmi utilizzati dalla classe base per gestirli.

Si tratta di una decisione basata sul giudizio di progettazione, basata sulla semplicità della variabile membro e su come dovrebbe essere tale nelle versioni future.

L'incapsulamento è ottimo, ma può essere portato troppo lontano.Ho visto classi i cui metodi privati ​​accedevano alle variabili membro utilizzando solo metodi getter/setter.Questo è eccessivo, poiché se una classe non può fidarsi dei propri metodi privati ​​con i propri dati privati, di chi può fidarsi?

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top