Membri dei dati pubblici vs Getters, Setters

Attualmente sto lavorando in Qt e quindi in C ++. Sto avendo lezioni che hanno membri di dati privati ​​e funzioni di membri pubblici. Ho getter e setter pubblici per i membri dei dati disponibili nella class.

Ora la mia domanda è, se abbiamo getter e setter per i membri dei dati nelle nostre classi, allora qual è il punto nel rendere questi dati membri come privati? Sono d’accordo che avere membri di dati privati ​​nelle classi Base suona logico. Ma oltre a questo, avere membri privati ​​e anche i loro getter e setter non mi sembra logico.

O possiamo invece rendere pubbliche tutte le variabili in modo tale da non aver bisogno di getter e setter? È una buona pratica averli? So che avere membri privati ​​garantisce l’astrazione dei dati, ma i getter e i setter consentono effettivamente l’accesso a tali variabili abbastanza facilmente. Qualsiasi suggerimento su questo è benvenuto.

Nessuno dei due. Dovresti avere metodi che facciano cose. Se una di quelle cose accade corrispondere con una specifica variabile interna è grandiosa, ma non dovrebbe esserci nulla che telegrafa questo agli utenti della tua class.

I dati privati ​​sono privati, quindi puoi sostituire l’implementazione ogni volta che vuoi (e puoi fare ricostruzioni complete ma questo è un problema diverso). Una volta che hai lasciato il Genie fuori dalla bottiglia, ti sarà imansible riportarlo indietro.

EDIT: A seguito di un commento ho fatto ad un’altra risposta.

Il mio punto qui è che stai facendo la domanda sbagliata. Non esiste una best practice in merito all’utilizzo di getter / setter o di membri pubblici. C’è solo ciò che è meglio per il tuo object specifico e come modella una specifica cosa del mondo reale (o cosa immaginaria, forse nel caso del gioco).

I getter / setter personali sono il minore di due mali. Perché una volta che si inizia a creare getter / setter, le persone smettono di progettare oggetti con un occhio critico verso quali dati devono essere visibili e quali dati non dovrebbero. Con i membri pubblici è anche peggio perché la tendenza diventa rendere pubblico tutto.

Invece, esamina cosa fa l’object e cosa significa che qualcosa deve essere quell’object. Quindi creare metodi che forniscano un’interfaccia naturale in quell’object. Quell’interfaccia naturale implica l’esposizione di alcune proprietà interne usando getter e setter così sia. Ma la parte importante è che ci hai pensato prima del tempo e creato i getter / setter per un motivo giustificato del design.

No, non è nemmeno lontanamente la stessa cosa.

Esistono diversi livelli di protezione / occultamento dell’implementazione che possono essere raggiunti con approcci diversi a un’interfaccia di class:

1. Membro di dati pubblici:

  • fornisce sia l’accesso in lettura che in scrittura (se non costante) al membro dati
  • espone il fatto che l’object dati esiste fisicamente ed è fisicamente un membro di questa class (consente di creare puntatori di tipo puntatore-membro a quel membro dati)
  • fornisce l’accesso lvalue al membro dati (consente di creare puntatori ordinari per il membro)

2. Un metodo che restituisce un riferimento a un pezzo di dati (eventualmente a un membro di dati privato):

  • fornisce sia l’accesso in lettura che in scrittura (se non costante) ai dati
  • espone il fatto che l’object dati esiste fisicamente ma non espone che è fisicamente un membro di questa class (non consente di creare puntatori di tipo puntatore-membro ai dati)
  • fornisce accesso lvalue ai dati (consente di creare puntatori ordinari ad essi)

3. Metodi Getter e / o setter (possibilmente accedendo a un membro di dati privato):

  • fornisce sia l’accesso in lettura che in scrittura alla proprietà
  • non espone il fatto che l’object di dati esiste fisicamente, per non parlare fisicamente presente in questa class (non consente di creare puntatori di tipo puntatore-membro a quei dati, o qualsiasi tipo di puntatori per quella materia)
  • non fornisce l’accesso lvalue ai dati (non consente di creare segnalatori ordinari ad essi)

L’approccio getter / setter non espone nemmeno il fatto che la proprietà è implementata da un object fisico. Cioè potrebbe non esserci alcun membro di dati fisici dietro la coppia getter / setter.

Prendendo in considerazione l’account, è strano vedere qualcuno affermare che una coppia getter e setter è la stessa di un membro pubblico dei dati. In realtà, non hanno nulla in comune.

Naturalmente, ci sono variazioni di ciascun approccio. Un metodo getter, ad esempio, potrebbe restituire un riferimento const ai dati, che lo posizionerebbero da qualche parte tra (2) e (3).

Se hai getter e setter per ciascuno dei tuoi dati, non ha senso rendere privati ​​i dati. Ecco perché avere getter e setter per ognuno dei tuoi dati è una ctriggers idea. Considera la class std :: string – (probabilmente) ha UN getter, la funzione size () e nessun setter.

Oppure considera un object BankAccount – dovremmo avere setter SetBalance() per modificare il saldo corrente? No, la maggior parte delle banche non ti ringrazieranno per aver implementato una cosa del genere. Invece, vogliamo qualcosa come ApplyTransaction( Transaction & tx ) .

Rendi pubblici i dati. Nell’evento (piuttosto improbabile) che un giorno abbiate bisogno di logica nel “getter” o “setter”, potete cambiare il tipo di dati in una class proxy che sovraccarica l’ operator= e / o l’ operator T (dove T = qualunque sia il vostro tipo ” usando ora) per implementare la logica necessaria.

Modifica: l’idea che controllare l’accesso ai dati costituisca l’incapsulamento è fondamentalmente falsa. L’incapsulamento consiste nel hide i dettagli dell’implementazione (in generale!) Che non controllano l’accesso ai dati.

L’incapsulazione è complementare all’astrazione: l’astrazione si occupa del comportamento esternamente visibile dell’object, mentre l’incapsulamento si occupa di hide i dettagli di come tale comportamento è implementato.

L’uso di un getter o setter riduce effettivamente il livello di astrazione ed espone l’implementazione – richiede che il codice client sia consapevole che questa particolare class implementa ciò che è logicamente “dati” come una coppia di funzioni (il getter e il setter). L’utilizzo di un proxy come suggerito sopra fornisce un vero incapsulamento – tranne per un caso d’angolo oscuro, nasconde completamente il fatto che ciò che è logicamente un dato di fatto viene effettivamente implementato tramite una coppia di funzioni.

Naturalmente, questo deve essere mantenuto nel contesto: per alcune classi, i “dati” non sono affatto una buona astrazione. In generale, se è ansible fornire operazioni di livello superiore anziché dati, è preferibile. Tuttavia, ci sono classi per le quali l’astrazione più utilizzabile è la lettura e la scrittura di dati – e quando questo è il caso, i dati (astratti) dovrebbero essere resi visibili proprio come qualsiasi altro dato. Il fatto che ottenere o impostare il valore possa implicare più della semplice copia di bit è un dettaglio di implementazione che dovrebbe essere nascosto all’utente.

Getter e setter consentono di applicare la logica all’input / output dei membri privati, controllando quindi l’accesso ai dati (incapsulamento a coloro che conoscono i loro termini OO).

Le variabili pubbliche lasciano i dati della tua class aperti al pubblico per una manipolazione incontrollata e non convalidata, che è quasi sempre indesiderabile.

Devi pensare anche a queste cose a lungo termine. Potresti non avere la convalida ora (motivo per cui le variabili pubbliche sembrano essere una buona idea) ma c’è la possibilità che vengano aggiunte lungo la strada. Aggiungendoli prima del tempo lascia il framework in modo che ci sia meno ri-fattorizzazione del raod, per non parlare della convalida che non romperà il codice dipendente in questo modo).

Tenete a mente, però, che non significa che ogni variabile privata abbia bisogno del proprio getter / setter. Neil mostra un buon punto nel suo esempio bancario che a volte Getters / Setters non ha senso.

Se sei abbastanza sicuro che la tua logica è semplice e non hai mai bisogno di fare qualcos’altro quando leggi / scrivi una variabile, è meglio mantenere i dati pubblici. Nel caso C ++, preferisco usare struct anziché class per sottolineare il fatto che i dati sono pubblici.

Tuttavia, molto spesso devi fare altre cose quando accedi ai membri dei dati, o vuoi darti la libertà di aggiungere questa logica in seguito. In questo caso, getter e setter sono una buona idea. La tua modifica sarà trasparente per i clienti del tuo codice.

Un semplice esempio di funzionalità aggiuntive: potresti voler registrare una stringa di debug ogni volta che accedi a una variabile.

A parte i problemi di incapsulamento (che sono una ragione sufficiente), è molto facile impostare un punto di interruzione ogni volta che si imposta / accede la variabile quando si hanno getter / setter.

I motivi per utilizzare i campi pubblici piuttosto che getter e setter includono:

  1. Non ci sono valori illegali.
  2. Il cliente dovrebbe modificarlo.
  3. Per essere in grado di scrivere cose come object.XY = Z.
  4. Fare una forte promise che il valore è solo un valore e non ci sono effetti collaterali associati (e non lo sarà neanche in futuro).

A seconda del tipo di software su cui lavori, questi potrebbero essere tutti casi eccezionali (e se pensi di averne trovato uno probabilmente sbagli) o potrebbero verificarsi in ogni momento. Dipende davvero.

(Da dieci domande sulla programmazione basata sui valori ).

Su base strettamente pratica, ti suggerirei di iniziare rendendo privati ​​tutti i tuoi membri dei dati e rendendo privati ​​i loro getter e setter. Man mano che scopri cosa il resto del mondo (ad esempio, la tua “(l) comunità di utenti”) ha effettivamente bisogno, puoi esporre i getter e / o setter appropriati o scrivere accessorati pubblici controllati in modo appropriato.

Inoltre (a beneficio di Neil), durante il tempo di debug, a volte è utile avere un posto comodo dove appendere le stampe di debug e altre azioni, quando un particolare membro di dati viene letto o scritto. Con getter e setter, questo è facile. Con i membri dei dati pubblici, è un enorme dolore nel posteriore.

Ho sempre pensato che getter e setter siano volutamente prolissi nella maggior parte dei linguaggi di programmazione, in particolare per farti pensare due volte a usarli – perché il tuo interlocutore deve sapere del funzionamento interno della tua class dovrebbe essere la domanda in testa alla tua mente .

Credo che usare getter e setter semplicemente per ottenere e impostare il valore sia inutile. Non c’è differenza tra un membro pubblico e uno privato con tali metodi. Usa getter e setter solo quando hai bisogno di controllare i valori in qualche modo o quando pensi che possa essere utile in futuro (l’aggiunta di una logica non ti farà modificare il resto del codice).

Come riferimento, leggi le linee guida C ++ (C.131)

Suggerisco di non avere membri di dati pubblici (eccetto per le strutture POD). Inoltre, non raccomando di avere getter e setter per tutti i membri dei tuoi dati. Piuttosto, definisci un’interfaccia pubblica pulita per la tua class. Questo può includere metodi che ottengono e / o impostano valori di proprietà e tali proprietà possono essere implementate come variabili membro. Ma non rendere getter e setter per tutti i tuoi membri.

L’idea è di separare la tua interfaccia dalla tua implementazione, permettendoti di modificare l’implementazione senza che gli utenti della class debbano cambiare il loro codice. Se esponi tutto tramite getter e setter, non hai migliorato nulla sull’utilizzo dei dati pubblici.

L’utilizzo di getter e setter consente di modificare il modo in cui si forniscono i valori all’utente.

Considera quanto segue:

 double premium; double tax; 

Quindi si scrive codice dappertutto utilizzando questo valore premium per ottenere il premio:

 double myPremium = class.premium; 

Le tue specifiche sono appena cambiate e premium dal punto di vista dell’utente deve essere premium + tax

Dovrai modificare ovunque che quel valore premium sia utilizzato nel tuo codice e aggiungere tax ad esso.

Se invece lo hai implementato come tale:

 double premium; double tax; double GetPremium(){return premium;}; 

Tutto il tuo codice utilizzerebbe GetPremium() e la tua modifica delle tax sarebbe una riga:

 double premium; double tax; double GetPremium(){return premium + tax;}; 

Il valore di ritorno influenza anche l’uso di getter e setter. È una differenza ottenere il valore di una variabile o ottenere l’accesso alla variabile membro dati privati. By-value mantiene l’integrità, il riferimento o il puntatore non tanto.

Getters e Setter esistono principalmente in modo che possiamo controllare come vengono recuperati i membri e come sono impostati. Getters e setter non esistono solo come un modo per accedere a un membro specifico, ma per garantire che prima di provare e impostare un membro, che forse soddisfi determinate condizioni, o se lo recuperiamo, potremmo controllare che ne restituiamo una copia membro nel caso di un tipo non primitivo. In generale, dovresti provare a usare g / s’ers quando vuoi mettere in pipeline come deve essere interagito un membro dati, senza che ciò possa causare l’utilizzo del membro in modo ad hoc.