Se una variabile ha getter e setter, dovrebbe essere pubblica?


23

Ho una classe con una variabile privata e la classe ha un getter e un setter per quella variabile. Perché non rendere pubblica quella variabile?

L'unico caso in cui penso che devi usare getter e setter è se devi fare qualche operazione oltre al set o al get. Esempio:

void my_class::set_variable(int x){
   /* Some operation like updating a log */
   this->variable = x;
}

10
Getter e setter sono lì per la protezione. Un giorno potresti voler fare this->variable = x + 5, o chiamare una UpdateStatisticsfunzione in setter, e in quei casi classinstancea->variable = 5causerà problemi.
Coder

1
Un altro esempio è: set_inch, set_centimeter, get_inch, get_centimeter, con alcune azioni spettrale.
rwong

Sì, getter e setter ti aiutano a controllare le variabili di istanza nel modo in cui desideri che siano controllate.
developerdoug

7
@Coder: puoi sempre aggiungere il tuo getter / setter quando se ne presenta la necessità? O è qualcosa che non puoi fare facilmente in C ++ (la mia esperienza è principalmente Delphi)?
Marjan Venema,

Questa domanda: programmers.stackexchange.com/questions/21802/… può essere di interesse.
Winston Ewert,

Risposte:


21

Hai mai sentito parlare di una proprietà?

Una proprietà è un campo che ha accessori "incorporati" (getter e setter). Java, ad esempio, non ha proprietà, ma si consiglia di scrivere i getter e i setter in un campo privato. C # ha proprietà.

Quindi, perché abbiamo bisogno di getter e setter? Fondamentalmente ne abbiamo bisogno per proteggere / proteggere il campo. Ad esempio, non si accede al campo nel riferimento di memoria, si accede a un metodo che quindi modificherà il campo (riferimento). Tale metodo è in grado di eseguire alcune operazioni che un utente non è disposto a conoscere ( incapsulando il comportamento ), come nel tuo esempio. Immagina, ad esempio, che una dozzina di classi utilizzino il tuo campo pubblico e che tu debba cambiare il modo in cui viene usato ... Dovresti guardare ognuna di quelle classi per cambiare il modo in cui stanno usando il campo ... No così "OOlysh".

Ma, ad esempio, se hai un campo booleano chiamato morto. Dovresti pensarci due volte prima di dichiarare setDead e isDead. Dovresti scrivere accessori che siano leggibili dall'uomo , ad esempio kill () invece di setDead.

Tuttavia, ci sono molti framework che presumono che tu stia seguendo la convenzione di denominazione JavaBean (parlando di Java qui), quindi, in quei casi, dovresti dichiarare tutti i getter e setter dopo la convinzione di denominazione.


2
Ora "lettura umana" è finalmente un argomento che posso "grok". Tutti gli altri argomenti che di solito ascolto sono una sorta di correzione futura che possono essere affrontati altrettanto facilmente quando si presenta la necessità ...
Marjan Venema

13
Il tuo disprezzo per le "prove future" è comune, ma fuorviato. Sì, puoi affrontare tali cambiamenti quando se ne presenta la necessità, se sei l'unico cliente di quel codice . Se non pubblichi mai la tua soluzione per clienti esterni, puoi semplicemente sostenere un debito tecnico e nessuno deve mai saperlo. Ma i progetti interni hanno l'abitudine di diventare pubblici quando meno te lo aspetti, e quindi guai a te.
Kilian Foth,

2
In realtà sollevi un punto fantastico. uccidere non è un set / get, è un'azione. Nasconde completamente l'implementazione - è il modo in cui codifichi OO. Le proprietà, d'altra parte, altrettanto stupide quanto i setter / getter - stupide anche perché la sintassi semplice incoraggia le persone ad aggiungerle alle variabili quando non sono nemmeno necessarie (OO-Furbar in attesa di accadere). Chi penserebbe di usare kill () quando potrebbero semplicemente aggiungere un tag "proprietà" alla loro bandiera morta?
Bill K,

1
La domanda è taggata come una domanda C ++. C ++ non supporta le proprietà, quindi perché dovresti pubblicare questo?
Thomas Eding,

1
@ThomasEding: C ++ è in qualche modo unico nell'avere operatori di assegnazione ignorabili. Mentre C ++ non usa il termine "proprietà" allo stesso modo di linguaggi come C # o VB.NET, il concetto è ancora lì. In C ++ è possibile utilizzare il sovraccarico dell'operatore in modo tale foo.bar = biz.baz+5da richiamare una funzione bizper valutarla baz, quindi aggiungerne cinque e chiamare una funzione fooper impostarla barsul valore risultante. Tali sovraccarichi non sono chiamati "proprietà", ma possono servire allo stesso scopo.
supercat

25

Questa non è l'opinione più popolare ma non vedo molta differenza.

I setter e i getter sono una pessima idea. Ci ho pensato e onestamente non riesco a trovare una differenza tra un setter / getter una proprietà e una variabile pubblica in pratica.

In TEORIA un setter, un getter o una proprietà aggiungono un posto per intraprendere alcune azioni extra quando viene impostata / ottenuta una variabile e in teoria isolano il tuo codice dai cambiamenti.

In realtà raramente vedo setter e getter usati per aggiungere un'azione, e quando vuoi aggiungere un'azione vuoi aggiungerla a TUTTI i setter o getter di una classe (come il logging) che dovrebbe farti pensare che dovrebbe essere una soluzione migliore.

Per quanto riguarda l'isolamento delle decisioni di progettazione, se cambi un int in un lungo devi ancora cambiare i tuoi setter e almeno controllare ogni linea che li accede a mano - non molto isolamento lì.

Le classi mutabili dovrebbero essere comunque evitate di default, quindi aggiungere un setter dovrebbe essere l'ultima risorsa. Ciò viene mitigato con il modello del generatore in cui è possibile impostare un valore fino a quando l'oggetto non si trova nello stato desiderato, quindi la classe può diventare immutabile e i setter genereranno eccezioni.

Per quanto riguarda i getter - non riesco ancora a trovare molta differenza tra un getter e una variabile finale pubblica. Il problema qui è che è OO cattivo in entrambi i casi. Non dovresti chiedere un valore a un oggetto e operare su di esso - dovresti chiedere a un oggetto di eseguire un'operazione per te.

A proposito, non sto affatto sostenendo le variabili pubbliche - sto dicendo che setter e getter (e persino proprietà) sono troppo vicini per essere già variabili pubbliche.

Il grosso problema è semplicemente che le persone che non sono programmatori OO sono troppo tentate per usare setter e getter per trasformare oggetti in sfere di proprietà (strutture) che vengono passate e utilizzate, praticamente l'opposto di come funziona il codice orientato agli oggetti.


Una cosa positiva a parte le altre risposte qui, sul setter getter è: posso aggiungere alcune convalide / conversioni al suo interno prima che la variabile privata effettiva assuma il suo valore.
WinW

1
@Kiran vero, ma se lo imposti nel costruttore puoi avere delle validazioni E hai una classe immutabile. Per i setter non sto dicendo che una variabile scrivibile pubblicamente sia buona, sto dicendo che anche i setter sono cattivi. Per i getter potrei considerare una variabile finale pubblica un'alternativa praticabile.
Bill K,

In alcune lingue come C #, le proprietà non sono binarie compatibili con le variabili pubbliche, quindi se rendi una variabile pubblica parte dell'API, ma vuoi aggiungere un po 'di convalida, romperai il programma del tuo client quando lo converti in una proprietà. Meglio renderlo una proprietà pubblica sin dall'inizio.
Robert Harvey,

@RobertHarvey Ciò significa che stai esponendo proprietà. Il mio intero punto era che le proprietà di esposizione dovrebbero essere evitate e, se necessario, dovresti cercare di limitarle ai vincitori e rendere immutabile la tua classe. Se la tua classe è immutabile, perché dovresti aver bisogno del codice in un getter - e quindi ne consegue che potresti non avere un getter. Se non sei in grado di scrivere una classe senza proprietà mutabili esposte, allora sì, un setter è sempre meglio di una variabile pubblica scrivibile (e sparare ai piedi è sempre meglio che essere pugnalato nell'intestino).
Bill K,

1
Lo stato mutevole è accettabile, ma ancora una volta dovresti chiedere al tuo oggetto di fare qualcosa per te, non fare qualcosa al tuo oggetto - quindi quando muti il ​​tuo stato un setter non è un ottimo modo per farlo, prova a scrivere un metodo di business con la logica in esso invece. Ad esempio, "move (Up5) anziché setZLocation (getZLocation () + 5)".
Bill K,

8

L'utente di getter e setter entra nel principio dell'incapsulamento . Questo ti permetterà di cambiare il modo in cui le cose funzionano all'interno della classe e mantenere tutto funzionante.

Ad esempio, se altri 3 oggetti chiamano foo.barper ottenere il valore di bar e decidi di cambiare il nome di bar in modo da avere un problema nelle tue mani. Se gli oggetti chiamati foo.bardovessi cambiare tutte le classi che hanno questo. Se viene utilizzato un setter / getter, non hai nulla da cambiare. Un'altra possibilità è cambiare il tipo di variabile, nel qual caso basta aggiungere un po 'di codice di trasformazione al getter / setter e tu stai bene.


1
+1: la variabile membro è implementazione, getter e setter sono interfaccia. Solo l'interfaccia dovrebbe essere pubblica. Inoltre, i nomi getter e setter dovrebbero avere senso in termini di astrazione, indipendentemente dal fatto che vi sia una semplice variabile membro sottostante o qualche altra implementazione. Ci sono eccezioni - casi in cui un sottosistema creato da classi strettamente accoppiate è il modo più semplice - ma ciò spinge comunque il limite di occultamento dei dati su un livello comunque, alla classe che rappresenta l'intero sottosistema.
Steve314

Non potrei semplicemente presentare il getter e / o il setter quando devo cambiare il lavoro all'interno della classe? È qualcosa che si fa facilmente in Delfi, ma forse non in altre lingue?
Marjan Venema,

@ marjan Certo, potresti introdurre il getter / setter in qualsiasi momento in seguito. Il problema quindi è che devi tornare indietro e cambiare TUTTO il codice che utilizzava il campo pubblico invece di getter / setter. Non sono sicuro che io sia quello che è confuso o tu lo sei. 0.o
Pete

3
Personalmente, penso che questo sia un po 'un argomento falso. Se modifichi il significato di una variabile ma la variabile ha un getter e un setter, non importa come si chiama. Non è una parte visibile dell'interfaccia. È molto più probabile che tu voglia cambiare il nome dei getter e dei setter stessi per indicare ai clienti la modifica o il chiarimento del significato della variabile incapsulata. In quest'ultimo caso, non ti trovi in ​​una posizione migliore rispetto a una variabile pubblica. Ciò è del tutto diverso dall'argomento secondo cui una variabile con getter e setter è più esposta che incapsulata.
CB Bailey,

1
Un'operazione di refactoring è praticamente gratuita in entrambi i casi, quindi non la compro davvero. Inoltre è Very Bad Form usare un setter o un getter a meno che non ne abbia assolutamente bisogno - alla fine otterrai persone che non capiscono il concetto semplicemente aggiungendo setter e getter per i membri invece che quando sono necessari che gocciolano semi del male in tutta la tua base di codice.
Bill K,

5

L'uso di getter e setter ti consente anche di controllare quali contenuti vengono archiviati in una particolare variabile. Se il contenuto deve essere di un certo tipo o valore, una parte del codice setter può essere quella di garantire che il nuovo valore soddisfi questi requisiti. Se la variabile è pubblica non è possibile garantire che tali requisiti siano soddisfatti.

Questo approccio rende anche il tuo codice più adattabile e gestibile. È molto più semplice apportare modifiche all'architettura di una classe se si dispone di funzioni che mantengono quell'architettura nascosta da tutte le altre classi o funzioni che utilizzano quella classe. La già menzionata modifica di un nome di variabile è solo una delle molte modifiche che sono molto più facili da apportare se si dispone di funzioni come getter e setter. L'idea generale è quella di mantenere il più privato possibile, in particolare le variabili di classe.


Molte grazie! Stavo pensando allo stesso caso che hai citato. Se voglio che una variabile sia contenuta in [0,1] dovrei controllare il valore in entrata nel suo setter :)
Oni

Quando sei l'unico sviluppatore a lavorare su un progetto, è facile pensare che cose del genere siano inutili, ma quando ti ritrovi a lavorare con un team, scoprirai che avere l'abitudine di tecniche come questa ti tornerà molto utile e ti aiuterà evitare molti bug lungo la strada.
Kenneth,

Se si utilizza C #, utilizzare le proprietà, è possibile utilizzare una proprietà per impostare un'istanza privata, se necessario, in base a una logica nella parte setter della proprietà.
developerdoug

2

Dici "L'unico caso in cui penso che devi usare getter e setter è se devi fare qualche operazione oltre al set o al get".

Dovresti usare getter e setter se in futuro potresti dover fare qualche operazione oltre al set e get e non vuoi cambiare migliaia di righe di codice sorgente quando ciò accade.

Dovresti usare getter e setter se non vuoi che qualcuno prenda l'indirizzo della variabile e lo passi in giro, con conseguenze disastrose se quella variabile può quindi essere cambiata senza alcun codice sorgente che la menziona, o anche dopo che l'oggetto ha smesso di esistere più .


Il tuo ultimo paragrafo sottolinea un punto eccellente che taglia in entrambi i modi: richiedere l'uso di getter e setter impedirà ad altro codice di prendere l'indirizzo di qualcosa. Nei casi in cui ci sarebbero vantaggi significativi e pochi svantaggi nell'avere il codice esterno prendere gli indirizzi delle cose, tale restrizione potrebbe essere una cosa negativa. se ci fossero pochi benefici e gravi svantaggi nel consentire tale comportamento, limitarlo potrebbe essere una buona cosa.
supercat

1

Prima di tutto, chiariamo il paradigma.

  • Strutture di dati -> un layout di memoria che può essere attraversato e manipolato da funzioni adeguatamente informate.
  • Oggetti -> un modulo autonomo che nasconde la sua implementazione e fornisce un'interfaccia che può essere comunicata attraverso.

Dove è utile un getter / setter?

Getter / setter sono utili in Strutture dati? No.

Una struttura dati è una specifica del layout di memoria comune e manipolata da una famiglia di funzioni.

Generalmente qualsiasi nuova nuova funzione può venire e manipolare una struttura di dati, se lo fa in modo che le altre funzioni possano ancora capirla, allora la funzione si unisce alla famiglia. Altrimenti è una funzione canaglia e una fonte di bug.

Non fraintendetemi, potrebbero esserci diverse famiglie di funzioni che combattono su quella struttura di dati con spie, cappotti e doppi agenti ovunque. Va bene quando ognuno ha la propria struttura di dati con cui giocare, ma quando lo condividono ... immagina che diverse famiglie criminali non sono d'accordo sulla politica, può diventare un disastro molto velocemente.

Dato il disordine che le famiglie di funzioni estese possono raggiungere, c'è un modo per codificare la struttura dei dati in modo che le funzioni non autorizzate non rovinino tutto? Sì, si chiamano oggetti.

Getter / setter sono utili in Oggetti? No.

Lo scopo principale di avvolgere una struttura di dati in un oggetto era garantire che non esistessero funzioni canaglia. Se la funzione voleva unirsi alla famiglia, prima doveva essere accuratamente controllata e quindi diventare parte dell'oggetto.

Il punto / scopo di un getter e di un setter è quello di consentire alle funzioni esterne all'oggetto di modificare direttamente il layout di memoria dell'oggetto. Sembra una porta aperta da permettere ai ladri ...

The Edge Case

Ci sono due situazioni in cui un getter / setter pubblico ha un senso.

  • Una parte della struttura di dati all'interno dell'oggetto è gestita dall'oggetto, ma non controllata dall'oggetto.
  • Un'interfaccia che descrive un'astrazione di alto livello di una struttura di dati in cui si prevede che alcuni elementi non abbiano il controllo dell'oggetto di implementazione.

I contenitori e le interfacce contenitore sono esempi perfetti di entrambe queste due situazioni. Il contenitore gestisce internamente le strutture di dati (elenco collegato, mappa, albero) ma il controllo manuale sull'elemento specifico è a tutti quanti. L'interfaccia lo sottrae, ignora completamente l'implementazione e descrive solo le aspettative.

Sfortunatamente molte implementazioni sbagliano e definiscono l'interfaccia di questo tipo di oggetti per dare accesso diretto all'oggetto reale. Qualcosa di simile a:

interface Container<T>
{
    typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
    TRef item(int index); 
}

Questo è rotto. Le implementazioni di Container devono esplicitamente consegnare il controllo dei propri interni a chiunque li usi. Devo ancora vedere un linguaggio a valore mutabile in cui questo va bene (i linguaggi con semantica a valore immutabile vanno per definizione bene dal punto di vista della corruzione dei dati, ma non necessariamente dal punto di vista dello spionaggio dei dati).

È possibile migliorare / correggere i getter / setter utilizzando solo la semantica della copia o utilizzando un proxy:

interface Proxy<T>
{
     operator T(); //<returns a copy
     ... operator ->(); //<permits a function call to be forwarded to an element
     Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
}

interface Container<T>
{
     Proxy<T> item(int index);
     T item(int index); //<When T is a copy of the original value.
     void item(int index, T new_value); //<where new_value is used to replace the old value
}

Probabilmente una funzione canaglia potrebbe ancora giocare a caos qui (con sufficiente sforzo la maggior parte delle cose sono possibili), ma la semantica della copia e / o il proxy riduce la possibilità di un numero di errori.

  • straripamento
  • underflow
  • le interazioni con l'elemento secondario sono verificate / verificabili (nel tipo perdere le lingue questo è un vantaggio)
  • L'elemento reale può o meno essere residente in memoria.

Getter / setter privati

Questo è l'ultimo bastione di getter e setter che lavorano direttamente sul tipo. In realtà non chiamerei nemmeno questi getter e setter, ma accessori e manipolatori.

In questo contesto a volte la manipolazione di una parte specifica della struttura dei dati richiede sempre / quasi sempre / generalmente che si verifichi una contabilità specifica. Ad esempio, quando si aggiorna la radice di un albero, è necessario eliminare la cache di ricerca o quando si accede all'elemento di dati esterno, è necessario ottenere / rilasciare un blocco. In questi casi ha senso applicare il principio DRY e suddividere insieme tali azioni.

Nel contesto privato, è ancora possibile che le altre funzioni della famiglia facciano da parte questi "getter e setter" e manipolino la struttura dei dati. Ecco perché li considero più come accessori e manipolatori. Puoi accedere direttamente ai dati o fare affidamento su un altro membro della famiglia per ottenere quella parte corretta.

Getter / setter protetti

In un contesto protetto, non è terribilmente diverso da un contesto pubblico. Eventuali funzioni straniere potenzialmente indesiderate richiedono l'accesso alla struttura dei dati. Quindi no, se esistono, operano come getter / setter pubblici.


0

Dall'esperienza C ++, il setter / getter è utile per 2 scenari:

  1. se è necessario eseguire un controllo di integrità prima di assegnare valore al membro come stringhe o array.
  2. può essere utile quando è necessario il sovraccarico della funzione come il valore int per boolineare l'assegnazione quando -1 (o valori negativi) è falso. e viceversa per getter.

A parte questo, la sicurezza è un punto valido ma la sua importanza è limitata a pochissime applicazioni di sviluppo come i moduli relativi al login o all'accesso db. Perché preoccuparsi della codifica extra quando solo poche persone usano il tuo modulo?

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.