L'incapsulamento ha uno scopo, ma può anche essere abusato o abusato.
Considera qualcosa come l'API Android che ha classi con dozzine (se non centinaia) di campi. Esporre quei campi in cui il consumatore dell'API rende più difficile la navigazione e l'uso, inoltre dà all'utente la falsa idea di poter fare tutto ciò che vuole con quei campi che potrebbero essere in conflitto con il modo in cui dovrebbero essere utilizzati. Quindi l'incapsulamento è ottimo in questo senso per manutenibilità, usabilità, leggibilità ed evitare bug pazzi.
D'altra parte, POD o semplici tipi di dati vecchi, come una struttura di C / C ++ in cui tutti i campi sono pubblici possono essere utili. Avere getter / setter inutili come quelli generati dall'annotazione @data in Lombok è solo un modo per mantenere il "modello di incapsulamento". Uno dei pochi motivi per cui facciamo getter / setter "inutili" in Java è che i metodi forniscono un contratto .
In Java, non è possibile avere campi in un'interfaccia, pertanto si utilizzano getter e setter per specificare una proprietà comune di tutti gli implementatori di tale interfaccia. In linguaggi più recenti come Kotlin o C # vediamo il concetto di proprietà come campi per i quali è possibile dichiarare setter e getter. Alla fine, i getter / setter inutili sono più un'eredità con cui Java deve convivere, a meno che Oracle non aggiunga proprietà ad esso. Kotlin, ad esempio, che è un altro linguaggio JVM sviluppato da JetBrains, ha classi di dati che sostanzialmente fanno ciò che fa l'annotazione @data in Lombok.
Anche qui ci sono alcuni esempi:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
Questo è un brutto caso di incapsulamento. Il getter e il setter sono effettivamente inutili. L'incapsulamento viene utilizzato principalmente perché questo è lo standard in linguaggi come Java. In realtà non aiuta, oltre a mantenere la coerenza attraverso la base di codice.
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
Questo è un buon esempio di incapsulamento. L'incapsulamento viene utilizzato per far rispettare un contratto, in questo caso IDataInterface. Lo scopo dell'incapsulamento in questo esempio è di fare in modo che il consumatore di questa classe utilizzi i metodi forniti dall'interfaccia. Anche se getter e setter non fanno nulla di speciale, ora abbiamo definito un tratto comune tra DataClass e altri implementatori di IDataInterface. Quindi posso avere un metodo come questo:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
Ora, quando parlo di incapsulamento, penso che sia importante anche affrontare il problema della sintassi. Vedo spesso persone lamentarsi della sintassi necessaria per imporre l'incapsulamento anziché l'incapsulamento stesso. Un esempio che viene in mente è di Casey Muratori (puoi vedere il suo sfogo qui ).
Supponi di avere una classe di giocatori che usa l'incapsulamento e desideri spostare la sua posizione di 1 unità. Il codice sarebbe simile al seguente:
player.setPosX(player.getPosX() + 1);
Senza incapsulamento sembrerebbe così:
player.posX++;
Qui sostiene che l'incapsulamento porta a scrivere molto di più senza vantaggi aggiuntivi e questo può in molti casi essere vero, ma si nota qualcosa. L'argomento è contrario alla sintassi, non all'incapsulamento stesso. Anche in linguaggi come C che non hanno il concetto di incapsulamento, vedrai spesso variabili in strutture pre-fissate o suffissate con '_' o 'my' o qualsiasi altra cosa per indicare che non dovrebbero essere usate dal consumatore dell'API, come se fossero privato.
Il fatto è che l'incapsulamento può aiutare a rendere il codice molto più gestibile e facile da usare. Considera questa classe:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
Se le variabili fossero pubbliche in questo esempio, un consumatore di questa API sarebbe confuso su quando usare posX e posY e quando usare setPosition (). Nascondendo questi dettagli aiuti il consumatore a utilizzare meglio la tua API in modo intuitivo.
La sintassi è tuttavia una limitazione in molte lingue. Tuttavia i linguaggi più recenti offrono proprietà che ci danno la bella sintassi dei membri del publice e i vantaggi dell'incapsulamento. Troverai le proprietà in C #, Kotlin, anche in C ++ se usi MSVC. ecco un esempio in Kotlin.
class VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}
Qui abbiamo realizzato le stesse cose dell'esempio Java, ma possiamo usare posX e posY come se fossero variabili pubbliche. Quando provo a cambiare il loro valore, però, verrà eseguito il corpo del setter set ().
In Kotlin, ad esempio, questo sarebbe l'equivalente di un bean Java con getter, setter, hashcode, uguale e toString implementati:
data class DataClass(var data: Int)
Notare come questa sintassi ci consente di eseguire un bean Java in una riga. Hai notato correttamente il problema che un linguaggio come Java ha nell'implementazione dell'incapsulamento, ma è colpa di Java non dell'incapsulamento stesso.
Hai detto che usi @Data di Lombok per generare getter e setter. Nota il nome, @Data. È principalmente pensato per essere utilizzato su classi di dati che archiviano solo dati e devono essere serializzati e deserializzati. Pensa a qualcosa come un file di salvataggio da un gioco. Ma in altri scenari, come con un elemento dell'interfaccia utente, si vogliono sicuramente setter perché semplicemente cambiare il valore di una variabile potrebbe non essere sufficiente per ottenere il comportamento previsto.
"It will create getters, setters and setting constructors for all private fields."
- Il modo in cui lei descrive questo strumento, suona come si mantiene l'incapsulamento. (Almeno in senso libero, automatizzato, un po 'anemico.) Qual è esattamente il problema?