La encapsulación me dice que haga que todos o casi todos los campos sean privados y los exponga por getters / setters. Pero ahora aparecen bibliotecas como Lombok que nos permiten exponer todos los campos privados mediante una breve anotación @Data
. Creará getters, setters y constructores de configuración para todos los campos privados.
¿Podría alguien explicarme cuál es la sensación de ocultar todos los campos como privados y luego exponerlos a todos con alguna tecnología adicional? ¿Por qué simplemente no usamos solo campos públicos entonces? Siento que hemos recorrido un camino largo y difícil solo para volver al punto de partida.
Sí, hay otras tecnologías que funcionan a través de getters y setters. Y no podemos usarlos a través de simples campos públicos. Sin embargo, estas tecnologías aparecido sólo porque teníamos esas numerosas propiedades - campos privados detrás de getters / setters públicas. Si no tuviéramos las propiedades, estas tecnologías se desarrollarían de otra manera y apoyarían los campos públicos. Y todo sería simple y no tendríamos ninguna necesidad de Lombok ahora.
¿Cuál es el sentido de todo este ciclo? ¿Y la encapsulación tiene realmente algún sentido ahora en la programación de la vida real?
fuente
"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?Respuestas:
Si expone todos sus atributos con getters / setters, obtendrá una estructura de datos que todavía se utiliza en C o en cualquier otro lenguaje de procedimiento. Se no encapsulado y Lombok es sólo hacer para trabajar con el código de procedimiento menos doloroso. Getters / setters tan malos como simples campos públicos. No hay diferencia realmente.
Y la estructura de datos no es un objeto. Si comenzará a crear un objeto escribiendo una interfaz, nunca agregará getters / setters a la interfaz. Exponer sus atributos conduce a un código de procedimiento de espagueti donde la manipulación con datos está fuera de un objeto y se extiende por toda la base de código. Ahora se trata de datos y manipulaciones con datos en lugar de hablar con objetos. Con getters / setters, tendrá una programación procesal basada en datos donde la manipulación con eso se realiza de la manera imperativa. Obtenga datos, haga algo, establezca datos.
En OOP, la encapsulación es un elefante si se hace de la manera correcta. Debe encapsular el estado y los detalles de implementación para que el objeto tenga control total sobre eso. La lógica se enfocará dentro del objeto y no se extenderá por toda la base de código. Y sí, la encapsulación sigue siendo esencial en la programación, ya que el código será más fácil de mantener.
EDICIONES
Después de ver las discusiones en curso, quiero agregar varias cosas:
Entonces, si volvemos a la pregunta de por qué deberíamos hacer esto. Considere este simple ejemplo:
Aquí tenemos un documento que expone detalles internos por getter y tenemos un código de procedimiento externo en
printDocument
función que funciona con eso fuera del objeto. ¿Por qué es esto malo? Porque ahora solo tienes el código de estilo C. Sí, está estructurado, pero ¿cuál es realmente la diferencia? Puede estructurar funciones C en diferentes archivos y con nombres. Y esas llamadas capas están haciendo exactamente eso. La clase de servicio es solo un conjunto de procedimientos que funcionan con datos. Ese código es menos mantenible y tiene muchos inconvenientes.Compare con este. Ahora tenemos un contrato y los detalles de implementación de este contrato están ocultos dentro del objeto. Ahora realmente puede probar esa clase y esa clase está encapsulando algunos datos. La forma en que funciona con esos datos es una preocupación del objeto. Para hablar con el objeto ahora, debe pedirle que se imprima. Eso es encapsulación y eso es un objeto. Obtendrá todo el poder de la inyección de dependencia, burlas, pruebas, responsabilidades únicas y muchos beneficios con OOP.
fuente
El sentido es que se supone que no debes hacer eso .
La encapsulación significa que expone solo aquellos campos a los que realmente necesita otras clases para acceder, y que es muy selectivo y cuidadoso al respecto.
¡ No solo dé a todos los campos getters y setters por defecto!
Esto está completamente en contra del espíritu de la especificación JavaBeans, que es irónicamente de donde proviene el concepto de captadores y creadores públicos.
Pero si observa la especificación, verá que pretendía que la creación de getters y setters fuera muy selectiva, y que habla de propiedades de "solo lectura" (sin setter) y propiedades de "solo escritura" (no adquiridor).
Otro factor es que los captadores y establecedores no son necesariamente un simple acceso al campo privado . Un getter puede calcular el valor que devuelve de una manera arbitrariamente compleja, tal vez almacenarlo en caché. Un setter puede validar el valor o notificar a los oyentes.
Así que ahí estás: la encapsulación significa que expones solo esa funcionalidad que realmente necesitas exponer. Pero si no piensa en lo que necesita exponer y simplemente expone todo de manera involuntaria siguiendo alguna transformación sintáctica, por supuesto, eso no es realmente una encapsulación.
fuente
getFieldName()
convierten en un contrato automático para nosotros. No esperaremos un comportamiento complejo detrás de eso. En la mayoría de los casos, es solo una ruptura directa de la encapsulación.Creo que el quid de la cuestión se explica por su comentario:
El problema que tiene es que está mezclando el modelo de datos de persistencia con el modelo de datos activo .
Una aplicación generalmente tendrá múltiples modelos de datos:
encima del modelo de datos que realmente usa para realizar sus cálculos.
En general, los modelos de datos utilizados para la comunicación con el exterior deben estar aislados e independientes del modelo de datos interno (modelo de objeto de negocio, BOM) en el que se realizan los cálculos:
En este escenario, está perfectamente bien que los objetos utilizados en las capas de comunicación tengan todos los elementos públicos o expuestos por getters / setters. Esos son objetos simples sin invariantes en absoluto.
Por otro lado, su BOM debe tener invariantes, lo que generalmente impide tener muchos setters (los getters no afectan a los invariantes, aunque reducen la encapsulación en cierto grado).
fuente
Considera lo siguiente..
Tienes una
User
clase con una propiedadint age
:Desea ampliar esto para que
User
tenga una fecha de nacimiento, en lugar de solo una edad. Usando un getter:Podemos cambiar el
int age
campo por uno más complejoLocalDate dateOfBirth
:Sin violaciones de contrato, sin ruptura de código. Nada más que aumentar la representación interna en preparación para comportamientos más complejos.
El campo en sí está encapsulado.
Ahora, para aclarar las preocupaciones ...
La
@Data
anotación de Lombok es similar a las clases de datos de Kotlin .No todas las clases representan objetos de comportamiento. En cuanto a romper la encapsulación, depende de su uso de la función. No deberías exponer todos tus campos a través de captadores.
En un sentido más general, la encapsulación es el acto de ocultar información. Si abusa
@Data
, es fácil suponer que probablemente está rompiendo la encapsulación. Pero eso no quiere decir que no tenga ningún propósito. JavaBeans, por ejemplo, está mal visto por algunos. Sin embargo, se usa ampliamente en el desarrollo empresarial.¿Llegaría a la conclusión de que el desarrollo empresarial es malo debido al uso de frijoles? ¡Por supuesto no! Los requisitos difieren de los del desarrollo estándar. ¿Se puede abusar de los frijoles? ¡Por supuesto! ¡Se abusa de ellos todo el tiempo!
Lombok también es compatible
@Getter
e@Setter
independiente: use lo que sus requisitos requieren.fuente
@Data
anotación de un tipo, que por diseño desencapsula todos los campossetAge(xyz)
Así no se define la encapsulación en la programación orientada a objetos. La encapsulación significa que cada objeto debe ser como una cápsula, cuya capa externa (la API pública) protege y regula el acceso a su interior (los métodos y campos privados), y lo oculta de la vista. Al ocultar las partes internas, las personas que llaman no dependen de las partes internas, lo que permite cambiar las partes internas sin cambiar (o incluso volver a compilar) las llamadas. Además, la encapsulación permite que cada objeto imponga sus propios invariantes, haciendo que las operaciones seguras solo estén disponibles para las personas que llaman.
Por lo tanto, la encapsulación es un caso especial de ocultación de información, en el que cada objeto oculta sus elementos internos y hace cumplir sus invariantes.
Generar captadores y establecedores para todos los campos es una forma bastante débil de encapsulación, porque la estructura de los datos internos no está oculta y no se pueden aplicar invariantes. Sin embargo, tiene la ventaja de que podría cambiar la forma en que los datos se almacenan internamente (siempre y cuando pueda convertir ay desde la estructura anterior) sin tener que cambiar (o incluso volver a compilar) las personas que llaman.
Parcialmente, esto se debe a un accidente histórico. La primera es que, en Java, las expresiones de invocación de métodos y las expresiones de acceso a campos son sintácticamente diferentes en el sitio de la llamada, es decir, reemplazar un acceso de campo por una llamada getter o setter rompe la API de una clase. Por lo tanto, si necesita un descriptor de acceso, debe escribir uno ahora o ser capaz de romper la API. Esta ausencia de soporte de propiedad de nivel de lenguaje contrasta fuertemente con otros lenguajes modernos, especialmente C # y EcmaScript .
Strike 2 es que la especificación JavaBeans definió propiedades como getters / setters, los campos no eran propiedades. Como resultado, la mayoría de los marcos empresariales iniciales admitían getters / setters, pero no campos. Eso ya pasó hace mucho tiempo ( Java Persistence API (JPA) , Bean Validation , Java Architecture for XML Binding (JAXB) , Jacksontodos los campos de soporte están bien por ahora), pero los antiguos tutoriales y libros continúan persistiendo, y no todos saben que las cosas han cambiado. La ausencia de soporte de propiedad de nivel de lenguaje puede ser una molestia en algunos casos (por ejemplo, porque la carga diferida JPA de entidades individuales no se activa cuando se leen campos públicos), pero la mayoría de los campos públicos funcionan bien. A saber, mi empresa escribe todos los DTO para sus API REST con campos públicos (después de todo, no se hace más público el que se transmite por Internet :-).
Dicho esto, Lombok de
@Data
hace más de generar getters / setters: También generatoString()
,hashCode()
yequals(Object)
que puede ser muy valiosa.La encapsulación puede ser invaluable o completamente inútil, depende del objeto que se encapsula. En general, cuanto más compleja es la lógica dentro de la clase, mayor es el beneficio de la encapsulación.
Los captadores y establecedores generados automáticamente para cada campo generalmente se usan en exceso, pero pueden ser útiles para trabajar con marcos heredados o para usar la característica de marco ocasional que no es compatible con los campos.
La encapsulación se puede lograr con getters y métodos de comando. Los setters generalmente no son apropiados, ya que se espera que solo cambien un solo campo, mientras que mantener invariantes puede requerir que se cambien varios campos a la vez.
Resumen
getters / setters ofrecen una encapsulación bastante pobre.
La prevalencia de getters / setters en Java se debe a la falta de soporte de nivel de lenguaje para las propiedades y las elecciones de diseño cuestionables en su modelo de componente histórico que se han consagrado en muchos materiales de enseñanza y en los programadores enseñados por ellos.
Otros lenguajes orientados a objetos, como EcmaScript, admiten propiedades a nivel de lenguaje, por lo que se pueden introducir captadores sin romper la API. En dichos idiomas, los captadores se pueden introducir cuando realmente los necesita, en lugar de hacerlo con anticipación, por si acaso lo necesita un día, lo que lo convierte en una experiencia de programación mucho más agradable.
fuente
Me he hecho esa pregunta de hecho.
Sin embargo, no es del todo cierto. La prevalencia de getters / setters IMO es causada por la especificación Java Bean, que lo requiere; por lo tanto, es una característica de la programación no orientada a objetos sino de la programación orientada a Bean, por así decirlo. La diferencia entre los dos es la de la capa de abstracción en la que existen; Los frijoles son más una interfaz de sistema, es decir, en una capa superior. Se resumen a partir de la base OO, o al menos están destinados a, como siempre, las cosas se llevan demasiado lejos con la frecuencia suficiente.
Diría que es un poco desafortunado que esta cosa de Bean que sea omnipresente en la programación de Java no esté acompañada de la adición de una función de lenguaje Java correspondiente: estoy pensando en algo como el concepto de Propiedades en C #. Para aquellos que no lo saben, es una construcción del lenguaje que se ve así:
De todos modos, el meollo de la implementación real todavía se beneficia mucho de la encapsulación.
fuente
La respuesta simple aquí es: tienes toda la razón. Getters y setters eliminan la mayoría (pero no todos) del valor de la encapsulación. Esto no quiere decir que cada vez que tenga un método get y / o set esté rompiendo la encapsulación, pero si agrega accesores a todos los miembros privados de su clase, lo está haciendo mal.
Los getters son setters son omnipresentes en la programación Java porque el concepto JavaBean fue promovido como una forma de vincular dinámicamente la funcionalidad al código preconstruido. Por ejemplo, podría tener un formulario en un applet (¿alguien recuerda esos?) Que inspeccionaría su objeto, buscaría todas las propiedades y mostraría los campos como. Luego, la interfaz de usuario puede modificar esas propiedades según la entrada del usuario. Usted, como desarrollador, solo tiene que preocuparse por escribir la clase y poner allí cualquier validación o lógica comercial, etc.
Usando frijoles de ejemplo
Esta no es una idea terrible per se, pero nunca he sido un gran admirador del enfoque en Java. Simplemente va contra la corriente. Utilice Python, Groovy, etc. Algo que respalde este tipo de enfoque de forma más natural.
La cosa JavaBean se salió de control porque creó JOBOL, es decir, desarrolladores escritos en Java que no entienden OO. Básicamente, los objetos se convirtieron en nada más que bolsas de datos y toda la lógica se escribió afuera en métodos largos. Debido a que fue visto como normal, las personas como tú y yo que cuestionamos esto se consideraron chiflados. Últimamente he visto un cambio y esta no es una posición tan extraña
Lo vinculante de XML es una nuez dura. Probablemente este no sea un buen campo de batalla para enfrentarse a JavaBeans. Si tiene que construir estos JavaBeans, intente mantenerlos fuera del código real. Trátelos como parte de la capa de serialización.
fuente
[Serializable]
atributo y sus parientes. ¿Por qué escribir 101 métodos / clases de serialización específicos cuando puedes escribir uno aprovechando la reflexión?¿Cuánto podemos hacer sin captadores? ¿Es posible eliminarlos por completo? ¿Qué problemas crea esto? ¿Podríamos incluso prohibir la
return
palabra clave?Resulta que puedes hacer mucho si estás dispuesto a hacer el trabajo. Entonces, ¿cómo sale la información de este objeto totalmente encapsulado? A través de un colaborador.
En lugar de dejar que el código te haga preguntas, dices cosas que hacer. Si esas cosas tampoco devuelven cosas, no tiene que hacer nada al respecto. Entonces, cuando esté pensando en buscar un
return
colaborador de puerto de salida, intente buscar lo que quede por hacer.Hacer las cosas de esta manera tiene beneficios y consecuencias. Tienes que pensar en algo más que lo que hubieras devuelto. Tienes que pensar cómo vas a enviar eso como un mensaje a un objeto que no lo solicitó. Puede ser que pases el mismo objeto que hubieras devuelto, o podría ser simplemente llamar a un método es suficiente. Hacer ese pensamiento tiene un costo.
El beneficio es que ahora estás hablando frente a la interfaz. Esto significa que obtienes todos los beneficios de la abstracción.
También le da un envío polimórfico, porque si bien sabe lo que está diciendo, no tiene que saber exactamente a qué se lo está diciendo.
Puede pensar que esto significa que tiene que ir solo en una dirección a través de una pila de capas, pero resulta que puede usar el polimorfismo para retroceder sin crear dependencias cíclicas locas.
Podría verse así:
Si puedes codificar así, entonces usar getters es una opción. No es un mal necesario.
fuente
Este es un tema controvertido (como puede ver) porque un montón de dogmas y malentendidos se mezclan con las preocupaciones razonables con respecto al tema de los captadores y establecedores. Pero en resumen, no hay nada de malo
@Data
y no rompe la encapsulación.¿Por qué usar getters y setters en lugar de campos públicos?
Porque los captadores / establecedores proporcionan encapsulación. Si expone un valor como un campo público y luego cambia a calcular el valor sobre la marcha, entonces debe modificar todos los clientes que acceden al campo. Claramente esto es malo. Es un detalle de implementación si alguna propiedad de un objeto se almacena en un campo, se genera sobre la marcha o se obtiene de otro lugar, por lo que la diferencia no debe exponerse a los clientes. Getter / setters setter resuelve esto, ya que ocultan la implementación.
Pero si el captador / definidor solo refleja un campo privado subyacente, ¿no es tan malo?
¡No! El punto es que la encapsulación le permite cambiar la implementación sin afectar a los clientes. Un campo aún podría ser una forma perfectamente buena de almacenar valor, siempre y cuando los clientes no tengan que saberlo ni preocuparse.
Pero, ¿no son autogeneradores getters / setters de campos que rompen la encapsulación?
¡No, la encapsulación sigue ahí!
@Data
Las anotaciones son solo una forma conveniente de escribir pares getter / setter que utilizan un campo subyacente. Para el punto de vista de un cliente, esto es como un par getter / setter normal. Si decide reescribir la implementación, aún puede hacerlo sin afectar al cliente. Entonces obtienes lo mejor de ambos mundos: encapsulación y sintaxis concisa.¡Pero algunos dicen que getter / setters siempre son malos!
Existe una controversia separada, donde algunos creen que el patrón getter / setter siempre es malo, independientemente de la implementación subyacente. La idea es que no debe establecer u obtener valores de los objetos, sino que cualquier interacción entre objetos debe modelarse como mensajes en los que un objeto le pide a otro objeto que haga algo. Esto es principalmente un dogma de los primeros días del pensamiento orientado a objetos. El pensamiento ahora es que para ciertos patrones (por ejemplo, objetos de valor, objeto de transferencia de datos), los captadores / establecedores pueden ser perfectamente apropiados.
fuente
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:
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.
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:
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í:
Sin encapsulación se vería así:
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:
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:
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.
fuente
La encapsulación le brinda flexibilidad . Al separar la estructura y la interfaz, le permite cambiar la estructura sin cambiar la interfaz.
Por ejemplo, si encuentra que necesita calcular una propiedad basada en otros campos en lugar de inicializar el campo subyacente en la construcción, simplemente puede cambiar el captador. Si hubiera expuesto el campo directamente, debería cambiar la interfaz y realizar cambios en cada sitio de uso.
fuente
Trataré de ilustrar el espacio problemático de encapsulación y diseño de clase, y responderé su pregunta al final.
Como se menciona en otras respuestas, el propósito de la encapsulación es ocultar detalles internos de un objeto detrás de una API pública, que sirve como un contrato. El objeto es seguro para cambiar sus componentes internos porque sabe que solo se llama a través de la API pública.
Si tiene sentido tener campos públicos, o captadores / establecedores, o métodos de transacción de nivel superior o pasar mensajes depende de la naturaleza del dominio que se está modelando. En el libro Akka Concurrency (que puedo recomendar incluso si está algo desactualizado) encontrará un ejemplo que ilustra esto, que abreviaré aquí.
Considere una clase de usuario:
Esto funciona bien en un contexto de subproceso único. El dominio que se está modelando es el nombre de una persona, y los mecánicos pueden encapsular perfectamente la forma en que se almacena ese nombre.
Sin embargo, imagine que esto se debe proporcionar en un contexto de subprocesos múltiples. Supongamos que un hilo lee periódicamente el nombre:
Y otros dos hilos están luchando contra un tira y afloja, poniéndose a su vez a Hillary Clinton y Donald Trump . Cada uno necesita llamar a dos métodos. Principalmente, esto funciona bien, pero de vez en cuando vas a ver pasar a Hillary Trump o Donald Clinton .
No puede resolver este problema agregando un candado dentro de los configuradores, ya que el candado solo se mantiene mientras se establece el nombre o el apellido. La única solución a través del bloqueo es agregar un bloqueo alrededor de todo el objeto, pero eso interrumpe la encapsulación ya que el código de llamada debe administrar el bloqueo (y puede causar puntos muertos).
Como resultado, no hay una solución limpia a través del bloqueo. La solución limpia es encapsular las partes internas nuevamente haciéndolas más gruesas:
El nombre en sí se ha vuelto inmutable, y verá que sus miembros pueden ser públicos, porque ahora es un objeto de datos puro sin capacidad de modificarlo una vez creado. A su vez, la API pública de la clase Usuario se ha vuelto más gruesa, con solo un setter restante, por lo que el nombre solo se puede cambiar como un todo. Encapsula más de su estado interno detrás de la API.
Lo que ve en este ciclo son intentos de aplicar soluciones adecuadas para un conjunto específico de circunstancias de manera demasiado amplia. Un nivel adecuado de encapsulación requiere comprender el dominio que se está modelando y aplicar el nivel correcto de encapsulación. A veces esto significa que todos los campos son públicos, a veces (como en las aplicaciones de Akka) significa que no tiene ninguna API pública, excepto un método único para recibir mensajes. Sin embargo, el concepto de encapsulación en sí mismo, lo que significa la ocultación de elementos internos detrás de una API estable, es clave para programar software a escala, especialmente en sistemas de subprocesos múltiples.
fuente
Puedo pensar en un caso de uso en el que esto tiene sentido. Es posible que tenga una clase a la que originalmente accede a través de una simple API getter / setter Posteriormente, amplía o modifica para que ya no use los mismos campos, pero aún sea compatible con la misma API .
Un ejemplo un tanto artificial: un Punto que comienza como un par cartesiano con
p.x()
yp.y()
. Luego, realiza una nueva implementación o subclase que utiliza coordenadas polares, por lo que también puede llamarp.r()
yp.theta()
, pero su código de cliente que llamap.x()
yp.y()
sigue siendo válido. La clase misma se convierte transparentemente de la forma polar interna, es decir, loy()
haría ahorareturn r * sin(theta);
. (En este ejemplo, configurar solox()
oy()
no tiene tanto sentido, pero aún es posible).En este caso, es posible que te encuentres diciendo: "Me alegro de haberme molestado en declarar automáticamente getters y setters en lugar de hacer públicos los campos, o habría tenido que romper mi API allí".
fuente
No hay absolutamente ningún punto. Sin embargo, el hecho de que haga esa pregunta demuestra que no ha entendido lo que hace Lombok y que no comprende cómo escribir código OO con encapsulación. Retrocedamos un poco ...
Algunos datos para una instancia de clase siempre serán internos y nunca deben exponerse. Algunos datos para una instancia de clase deberán establecerse externamente, y algunos datos deberán pasarse de una instancia de clase. Sin embargo, es posible que queramos cambiar la forma en que la clase hace las cosas debajo de la superficie, por lo que utilizamos funciones que nos permiten obtener y establecer datos.
Algunos programas desean guardar el estado para las instancias de clase, por lo que pueden tener alguna interfaz de serialización. Agregamos más funciones que permiten que la instancia de clase almacene su estado en el almacenamiento y recupere su estado del almacenamiento. Esto mantiene la encapsulación porque la instancia de clase todavía tiene el control de sus propios datos. Es posible que estemos serializando datos privados, pero el resto del programa no tiene acceso a ellos (o más exactamente, mantenemos un muro chino al elegir no corromper deliberadamente esos datos privados), y la instancia de clase puede (y debería) Realice comprobaciones de integridad en la deserialización para asegurarse de que sus datos vuelvan correctamente.
A veces, los datos necesitan verificaciones de rango, verificaciones de integridad o cosas por el estilo. Escribir estas funciones nosotros mismos nos permite hacer todo eso. En este caso, no queremos o necesitamos Lombok, porque estamos haciendo todo eso nosotros mismos.
Sin embargo, con frecuencia, encuentra que un parámetro establecido externamente se almacena en una sola variable. En este caso, necesitaría cuatro funciones para obtener / establecer / serializar / deserializar el contenido de esa variable. Escribir estas cuatro funciones usted mismo cada vez lo ralentiza y es propenso a errores. Automatizar el proceso con Lombok acelera su desarrollo y elimina la posibilidad de errores.
Sí, sería posible hacer pública esa variable. En esta versión particular de código, sería funcionalmente idéntico. Pero regresemos a por qué estamos usando funciones: "Es posible que queramos cambiar la forma en que la clase hace las cosas debajo de la superficie ..." Si hace pública su variable, está restringiendo su código ahora y siempre para tener esta variable pública como La interfaz. Sin embargo, si usa funciones, o si usa Lombok para generar automáticamente esas funciones para usted, puede cambiar los datos subyacentes y la implementación subyacente en cualquier momento en el futuro.
¿Esto lo hace más claro?
fuente
En realidad no soy un desarrollador de Java; pero lo siguiente es prácticamente independiente de la plataforma.
Casi todo lo que escribimos utiliza getters y setters públicos que acceden a variables privadas. La mayoría de los captadores y colocadores son triviales. Pero cuando decidimos que el setter necesita recalcular algo o el setter hace alguna validación o necesitamos reenviar la propiedad a una propiedad de una variable miembro de esta clase, esto no se rompe completamente en todo el código y es compatible con binarios, por lo que puede intercambiar ese módulo.
Cuando decidimos que esta propiedad realmente se debe calcular sobre la marcha, todo el código que lo mira no tiene que cambiar y solo el código que escribe debe cambiar y el IDE puede encontrarlo por nosotros. Cuando decidimos que es un campo computable grabable (solo tuvimos que hacerlo algunas veces), también podemos hacerlo. Lo bueno es que algunos de estos cambios son compatibles con binarios (el cambio al campo calculado de solo lectura no es en teoría, pero podría estar en práctica de todos modos).
Terminamos con muchos captores triviales con setters complicados. También terminamos con bastantes captadores de caché. El resultado final es que puede suponer que los captadores son razonablemente baratos, pero los establecedores pueden no serlo. Por otro lado, somos lo suficientemente sabios como para decidir que los setters no persisten en el disco.
Pero tuve que localizar al tipo que estaba cambiando ciegamente todas las variables miembro a propiedades. No sabía qué era la adición atómica, por lo que cambió lo que realmente necesitaba ser una variable pública a una propiedad y rompió el código de una manera sutil.
fuente
Getters y setters son una medida "por si acaso" añadida con el fin de evitar la refactorización en el futuro si la estructura interna o los requisitos de acceso cambian durante el proceso de desarrollo.
Digamos que unos meses después del lanzamiento, su cliente le informa que uno de los campos de una clase a veces se establece en valores negativos, aunque en ese caso debería fijarse a 0 como máximo. Usando campos públicos, tendría que buscar cada asignación de este campo en toda su base de código para aplicar la función de fijación al valor que va a establecer, y tenga en cuenta que siempre tendrá que hacer eso al modificar este campo, Pero eso apesta. En cambio, si ya usabas getters y setter, habrías podido modificar tu método setField () para asegurarte de que esta sujeción siempre se aplique.
Ahora, el problema con los lenguajes "anticuados" como Java es que alientan el uso de métodos para este propósito, lo que hace que su código sea infinitamente más detallado. Es doloroso escribir y difícil de leer, por eso hemos estado usando IDE que mitigan este problema de una forma u otra. La mayoría de IDE generará automáticamente getters y setters para usted, y también los ocultará a menos que se le indique lo contrario. Lombok va un paso más allá y simplemente los genera procesalmente durante el tiempo de compilación para mantener su código extra elegante. Sin embargo, otros lenguajes más modernos simplemente han resuelto este problema de una forma u otra. Scala o lenguajes .NET, por ejemplo,
Por ejemplo, en VB .NET o C #, simplemente puede hacer que todos los campos tengan un formato simple, sin efectos secundarios, captadores y establecedores solo campos públicos, y luego hacerlos privados, cambiar su nombre y exponer una Propiedad con el nombre anterior de el campo, donde puede ajustar el comportamiento de acceso del campo, si lo necesita. Con Lombok, si necesita ajustar el comportamiento de un getter o setter, simplemente puede eliminar esas etiquetas cuando sea necesario y codificar las suyas con los nuevos requisitos, sabiendo con seguridad que no tendrá que refactorizar nada en otros archivos.
Básicamente, la forma en que su método accede a un campo debe ser transparente y uniforme. Los lenguajes modernos le permiten definir "métodos" con la misma sintaxis de acceso / llamada de un campo, por lo que estas modificaciones se pueden realizar a pedido sin pensarlo mucho durante el desarrollo temprano, pero Java lo obliga a hacer este trabajo con anticipación porque lo hace No tiene esta característica. Todo lo que hace Lombok es ahorrarte algo de tiempo, porque el idioma que estás utilizando no quería permitirte ahorrar tiempo para un método "por si acaso".
fuente
foo.bar
pero puede manejarse mediante una llamada al método. Afirma que esto es superior a la forma "anticuada" de hacer que una API parezca llamadas a métodosfoo.getBar()
. Parece que estamos de acuerdo en que los campos públicos son problemáticos, pero afirmo que la alternativa "anticuada" es superior a la "moderna", ya que toda nuestra API es simétrica (son todas las llamadas a métodos). En el enfoque "moderno", tenemos que decidir qué cosas deberían ser propiedades y cuáles deberían ser métodos, que complican demasiado todo (¡especialmente si usamos la reflexión!).Sí, es contradictorio. Primero encontré propiedades en Visual Basic. Hasta entonces, en otros idiomas, mi experiencia, no había envoltorios de propiedad alrededor de los campos. Solo campos públicos, privados y protegidos.
Las propiedades eran una especie de encapsulación. Entendí que las propiedades de Visual Basic eran una forma de controlar y manipular la salida de uno o más campos mientras ocultaba el campo explícito e incluso su tipo de datos, por ejemplo, emitiendo una fecha como una cadena en un formato particular. Pero incluso entonces, uno no está "ocultando el estado y exponiendo la funcionalidad" desde la perspectiva del objeto más grande.
Pero las propiedades se justifican a sí mismas debido a los captadores y establecedores de propiedades separadas. Exponer un campo sin una propiedad era todo o nada; si pudiera leerlo, podría cambiarlo. Así que ahora se puede justificar un diseño de clase débil con setters protegidos.
Entonces, ¿por qué nosotros / ellos simplemente no usamos métodos reales? Porque era Visual Basic (y VBScript ) (oooh! Aaah!), Codificando las masas (!), Y estaba de moda. Y así, una idiotocracia finalmente dominó.
fuente