La encapsulación tiene un propósito, pero también puede ser mal utilizada o abusada.
Considere algo como la API de Android que tiene clases con docenas (si no cientos) de campos. Exponer esos campos que el consumidor de la API dificulta su navegación y uso, también le da al usuario la falsa noción de que puede hacer lo que quiera con esos campos que pueden entrar en conflicto con la forma en que se supone que deben usarse. Por lo tanto, la encapsulación es excelente en ese sentido para la mantenibilidad, la usabilidad, la legibilidad y para evitar errores locos.
Por otro lado, POD o tipos de datos antiguos simples, como una estructura de C / C ++ en la que todos los campos son públicos también pueden ser útiles. Tener getters / setters inútiles como los generados por la anotación @data en Lombok es solo una forma de mantener el "patrón de encapsulación". Una de las pocas razones por las que hacemos getters / setters "inútiles" en Java es que los métodos proporcionan un contrato .
En Java, no puede tener campos en una interfaz, por lo tanto, utiliza getters y setters para especificar una propiedad común que tienen todos los implementadores de esa interfaz. En lenguajes más recientes como Kotlin o C # vemos el concepto de propiedades como campos para los que puede declarar un setter y getter. Al final, los getters / setters inútiles son más un legado con el que Java tiene que vivir, a menos que Oracle le agregue propiedades. Kotlin, por ejemplo, que es otro lenguaje JVM desarrollado por JetBrains, tiene clases de datos que básicamente hacen lo que hace la anotación @data en Lombok.
También aquí hay algunos ejemplos:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
Este es un mal caso de encapsulación. El captador y el colocador son efectivamente inútiles. La encapsulación se usa principalmente porque este es el estándar en lenguajes como Java. En realidad no ayuda, además de mantener la coherencia en la base del código.
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
Este es un buen ejemplo de encapsulación. La encapsulación se usa para hacer cumplir un contrato, en este caso IDataInterface. El propósito de la encapsulación en este ejemplo es hacer que el consumidor de esta clase use los métodos que proporciona la interfaz. Aunque getter y setter no hacen nada elegante, ahora hemos definido un rasgo común entre DataClass y otros implementadores de IDataInterface. Por lo tanto, puedo tener un método como este:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
Ahora, cuando hablamos de encapsulación, creo que es importante abordar también el problema de sintaxis. A menudo veo que la gente se queja de la sintaxis que es necesaria para imponer la encapsulación en lugar de la encapsulación misma. Un ejemplo que viene a la mente es de Casey Muratori (puedes ver su diatriba aquí ).
Suponga que tiene una clase de jugador que usa encapsulación y quiere mover su posición en 1 unidad. El código se vería así:
player.setPosX(player.getPosX() + 1);
Sin encapsulación se vería así:
player.posX++;
Aquí argumenta que las encapsulaciones conducen a escribir mucho más sin beneficios adicionales y esto puede ser cierto en muchos casos, pero note algo. El argumento es contra la sintaxis, no la encapsulación en sí. Incluso en lenguajes como C que carecen del concepto de encapsulación, a menudo verá variables en estructuras prefijadas o con el sufijo '_' o 'mi' o lo que sea para indicar que el consumidor de la API no debería usarlas, como si fueran privado.
El hecho es que la encapsulación puede ayudar a que el código sea mucho más fácil de mantener y fácil de usar. Considera esta clase:
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
}
}
Si las variables fueran públicas en este ejemplo, un consumidor de esta API estaría confundido sobre cuándo usar posX y posY y cuándo usar setPosition (). Al ocultar esos detalles, ayuda al consumidor a utilizar mejor su API de forma intuitiva.
Sin embargo, la sintaxis es una limitación en muchos idiomas. Sin embargo, los lenguajes más nuevos ofrecen propiedades que nos brindan la agradable sintaxis de los miembros de publice y los beneficios de la encapsulación. Encontrará propiedades en C #, Kotlin, incluso en C ++ si usa MSVC. Aquí hay un ejemplo en Kotlin.
clase VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}
Aquí logramos lo mismo que en el ejemplo de Java, pero podemos usar posX y posY como si fueran variables públicas. Sin embargo, cuando trato de cambiar su valor, se ejecutará el cuerpo del settter set ().
En Kotlin, por ejemplo, esto sería el equivalente de un Java Bean con getters, setters, hashcode, equals y toString implementados:
data class DataClass(var data: Int)
Observe cómo esta sintaxis nos permite hacer un Java Bean en una línea. Usted notó correctamente el problema que tiene un lenguaje como Java al implementar la encapsulación, pero eso es culpa de Java, no de la encapsulación en sí.
Dijiste que usas @Data de Lombok para generar captadores y establecedores. Observe el nombre, @Data. Principalmente está destinado a usarse en clases de datos que solo almacenan datos y están destinados a ser serializados y deserializados. Piensa en algo como guardar un archivo de un juego. Pero en otros escenarios, como con un elemento de la interfaz de usuario, lo más seguro es que desee establecer setters porque solo cambiar el valor de una variable puede no ser suficiente para obtener el comportamiento esperado.
"It will create getters, setters and setting constructors for all private fields."
- La forma en que describe esta herramienta, parece que está manteniendo la encapsulación. (Al menos en un sentido de modelo suelto, automatizado, algo anémico). Entonces, ¿cuál es exactamente el problema?