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.
this->variable = x + 5
, o chiamare unaUpdateStatistics
funzione in setter, e in quei casiclassinstancea->variable = 5
causerà problemi.