¿La encapsulación sigue siendo uno de los elefantes en los que se encuentra OOP?


97

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?


19
"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?
David

78
La encapsulación oculta la implementación interna del objeto detrás de su contrato público (generalmente, una interfaz). Getters y setters hacen exactamente lo contrario: exponen los elementos internos del objeto, por lo que el problema está en getters / setters, no en la encapsulación.

22
Las clases de datos de @VinceEmigh no tienen encapsulación . Las instancias de ellos son los valores en exactamente el sentido de que son primitivas
Caleth

10
@VinceEmigh usando JavaBeans no es OO , es de procedimiento . Que la literatura los llame "Objetos" es un error de la historia.
Caleth

28
He pensado mucho en esto a lo largo de los años; Creo que este es un caso en el que la intención de OOP difiere de la implementación. Después de estudiar Smalltalk está claro lo de encapsulación programación orientada a objetos fue destinado a ser (es decir, cada clase es como un equipo independiente, con métodos como el protocolo comunitario), aunque por razones hasta ahora desconocidas para mí, ha cogido definitivamente en la popularidad para que éstos objetos getter / setter que no ofrecen encapsulación conceptual (no ocultan nada, no administran nada y no tienen otra responsabilidad que los datos), y aún así usan propiedades.
jrh

Respuestas:


82

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:

  • No importa cuántos de tus atributos expongas a través de getters / setters y con qué cuidado lo estás haciendo. Ser más selectivo no hará que su código OOP con encapsulación. Cada atributo que exponga conducirá a algún procedimiento que trabaje con esos datos desnudos de manera imperativa. Simplemente difundirá su código con menos lentitud y será más selectivo. Eso no cambia el núcleo.
  • Sí, en los límites dentro del sistema se obtienen datos desnudos de otros sistemas o bases de datos. Pero estos datos son solo otro punto de encapsulación.
  • Los objetos deben ser confiables . Toda la idea de los objetos es ser responsable para que no necesite dar órdenes que sean directas e imperativas. En cambio, le está pidiendo a un objeto que haga lo que hace bien a través del contrato. Usted delega de manera segura la parte que actúa en el objeto. Los objetos encapsulan el estado y los detalles de implementación.

Entonces, si volvemos a la pregunta de por qué deberíamos hacer esto. Considere este simple ejemplo:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Aquí tenemos un documento que expone detalles internos por getter y tenemos un código de procedimiento externo en printDocumentfunció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.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

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.


Los comentarios no son para discusión extendida; Esta conversación se ha movido al chat .
maple_shaft

1
"Getters / setters tan malos como campos públicos simples" - eso no es cierto, al menos en muchos idiomas. Por lo general, no puede anular el acceso de campo simple, pero puede anular getters / setters. Eso le da versatilidad a las clases infantiles. También facilita cambiar el comportamiento de las clases. por ejemplo, puede comenzar con un captador / definidor respaldado por un campo privado y luego pasar a un campo diferente o calcular el valor de otros valores. Tampoco se puede hacer con campos simples. Algunos idiomas permiten que los campos tengan lo que es esencialmente getters y setters automáticos, pero Java no es ese idioma.
Kat

Eh, todavía no estoy convencido. Su ejemplo está bien, pero solo denota un estilo diferente de codificación, no el "verdadero correcto", que no existe. Tenga en cuenta que la mayoría de los idiomas son multiparadigm hoy en día y rara vez son puramente OO o puramente procesales. Pegarse tan duro a los "conceptos OO puros". Como ejemplo, no puede usar DI en su ejemplo porque acopló la lógica de impresión a una subclase. La impresión no debería ser parte de un documento: un documento imprimible expondría su parte imprimible (a través de un getter) a un servicio de impresora.
T. Sar

Un enfoque OO adecuado para esto, si tuviéramos un enfoque "OO puro", usaría algo que implemente una clase abstracta de PrinterService que solicite, a través de un mensaje, qué demonios debería imprimirse, usando una especie de GetPrintablePart. Lo que implementa PrinterService podría ser cualquier tipo de impresora: imprimir en un PDF, en la pantalla, en el TXT ... Su solución hace que sea imposible cambiar la lógica de impresión por otra cosa, terminando más acoplado y menos mantenible que tu ejemplo "equivocado".
T. Sar

El resultado final: Getters y Setters no son malos o rompen OOP: las personas que no entienden cómo usarlos lo son. Su caso de ejemplo es un ejemplo de libro de texto de personas a las que les falta el punto completo de cómo funciona DI. El ejemplo que "corrigió" ya estaba habilitado para DI, desacoplado, podría ser burlado fácilmente ... Realmente, ¿recuerda todo el asunto de "Preferir composición sobre herencia"? ¿Uno de los pilares OO? Simplemente lo arrojaste por la ventana por la forma en que refactorizaste el código. Esto no volaría en ninguna revisión seria de código.
T. Sar

76

¿Podría alguien explicarme, cuál es el sentido de ocultar todos los campos como privados y luego exponerlos a todos con alguna tecnología adicional? ¿Por qué no usamos simplemente los campos públicos entonces?

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.


20
"[G] etters y setters no son necesariamente un simple acceso" - Creo que ese es un punto clave. Si su código está accediendo a un campo por nombre, no puede cambiar el comportamiento más adelante. Si está utilizando obj.get (), puede hacerlo.
Dan Ambrogio

44
@jrh: Nunca he escuchado que se llame una mala práctica en Java, y es bastante común.
Michael Borgwardt

2
@MichaelBorgwardt Interesante; un poco fuera de tema, pero siempre me he preguntado por qué Microsoft recomendó no hacerlo para C #. Supongo que esas pautas implican que en C # lo único para lo que se pueden usar los establecedores es para convertir un valor dado a otro valor utilizado internamente, de una manera que no puede fallar o tener un valor no válido (por ejemplo, tener una propiedad PositionInches y una propiedad PositionCentimeter donde se convierte automáticamente internamente a mm)? No es un gran ejemplo, pero es lo mejor que puedo encontrar en este momento.
jrh

2
@jrh esa fuente dice que no lo haga para los captadores, a diferencia de los setters.
Capitán Man

3
@Gangnus y ¿realmente ves que alguien está ocultando algo de lógica dentro de getter / setter? Estamos tan acostumbrados que se 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.
Izbassar Tolegen

17

Creo que el quid de la cuestión se explica por su comentario:

Estoy totalmente de acuerdo con tu pensamiento. Pero en algún lugar tenemos que cargar objetos con datos. Por ejemplo, de XML. Y las plataformas actuales que lo soportan lo hacen a través de getters / setters, degradando así la calidad de nuestro código y nuestra forma de pensar. Lombok, en realidad, no es malo en sí mismo, pero su propia existencia muestra que tenemos algo malo.

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:

  • un modelo de datos para hablar con la base de datos,
  • un modelo de datos para leer el archivo de configuración,
  • un modelo de datos para hablar con otra aplicación,
  • ...

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:

  • independiente : para que pueda agregar / eliminar propiedades en la lista de materiales según sus requisitos sin tener que cambiar todos los clientes / servidores, ...
  • aislado : para que todos los cálculos se realicen en la lista de materiales, donde viven los invariantes, y que el cambio de un servicio a otro o la actualización de un servicio no provoque una ondulación en toda la base de código.

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).


3
No crearía getters y setters para objetos de comunicación. Solo haría públicos los campos. ¿Por qué crear más trabajo del que es útil?
immibis

3
@immibis: estoy de acuerdo en general, sin embargo, como lo señala el OP, algunos marcos requieren getters / setters, en cuyo caso solo tiene que cumplir. Tenga en cuenta que el OP está utilizando una biblioteca que los crea automáticamente con la aplicación de un solo atributo, por lo que es poco trabajo para él.
Matthieu M.

1
@Gangnus: La idea aquí es que los captadores / establecedores están aislados de la capa límite (E / S), donde es normal ensuciarse, pero el resto de la aplicación (código puro) no está contaminado y sigue siendo elegante.
Matthieu M.

2
@kubanczyk: la definición oficial es Business Object Model, que yo sepa. Corresponde aquí a lo que llamé el "modelo de datos interno", el modelo de datos en el que ejecuta la lógica en el núcleo "puro" de su aplicación.
Matthieu M.

1
Este es el enfoque pragmático. Vivimos en un mundo híbrido. Si las bibliotecas externas pudieran saber qué datos pasar a los constructores de BOM, entonces solo tendríamos BOM.
Adrian Iftode

12

Considera lo siguiente..

Tienes una Userclase con una propiedad int age:

class User {
    int age;
}

Desea ampliar esto para que Usertenga una fecha de nacimiento, en lugar de solo una edad. Usando un getter:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Podemos cambiar el int agecampo por uno más complejo LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of 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 @Dataanotació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.

La encapsulación se usa para ocultar los valores o el estado de un objeto de datos estructurados dentro de una clase

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 @Gettere @Setterindependiente: use lo que sus requisitos requieren.


2
Esto no está relacionado con pega en la @Dataanotación de un tipo, que por diseño desencapsula todos los campos
Caleth

1
No, los campos no están ocultos . Porque todo puede venir ysetAge(xyz)
Caleth

99
@Caleth Los campos están ocultos. Estás actuando como si los setters no pudieran tener condiciones previas y posteriores, lo cual es un caso de uso muy común para usar setters. Estás actuando como si un campo == propiedad, que incluso en lenguajes como C #, simplemente no es cierto, ya que ambos tienden a ser compatibles (como en C #). El campo se puede mover a otra clase, se puede cambiar por una representación más compleja ...
Vince Emigh

1
Y lo que digo es que eso no es importante
Caleth

2
@Caleth ¿Por qué no es importante? Eso no tiene ningún sentido. ¿Estás diciendo que la encapsulación no se aplica debido a que siente que no es importante en esta situación, a pesar de que se aplica, por definición, y tiene su casos de uso?
Vince Emigh

11

La encapsulación me dice que haga que todos o casi todos los campos sean privados y los exponga por getters / setters.

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.

¿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.

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 @Datahace más de generar getters / setters: También genera toString(), hashCode()y equals(Object)que puede ser muy valiosa.

¿Y la encapsulación tiene realmente algún sentido ahora en la programación de la vida real?

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.


1
hace cumplir sus invariantes? Es ingles? ¿Podría explicarlo a un no inglés, por favor? No seas demasiado complicado: algunas personas aquí no saben inglés lo suficientemente bien.
Gangnus

Me gusta tu base, me gustan tus pensamientos (+1), pero no el resultado práctico. Prefiero usar el campo privado y alguna biblioteca especial para cargar datos para ellos por reflexión. Solo que no entiendo por qué estás estableciendo tu respuesta como un argumento en mi contra.
Gangnus

@Gangnus Una invariante es una condición lógica que no cambia (lo que significa no y el significado variable varía / cambia). Muchas funciones tienen reglas que deben ser verdaderas antes y después de su ejecución (llamadas condiciones previas y condiciones posteriores), de lo contrario, hay un error en el código. Por ejemplo, una función puede requerir que su argumento no sea nulo (una condición previa) y puede arrojar una excepción si su argumento es nulo porque ese caso sería un error en el código (es decir, en informática, el código ha roto un invariante).
Pharap

@Gangus: No estoy seguro de dónde entra la reflexión en esta discusión; Lombok es un procesador de anotaciones, es decir, un complemento para el compilador de Java que emite código adicional durante la compilación. Pero claro, puedes usar lombok. Solo digo que en muchos casos, los campos públicos funcionan igual de bien y son más fáciles de configurar (no todos los compiladores detectan los procesadores de anotaciones automáticamente ...).
meriton

1
@Gangnus: las invariantes son las mismas en matemáticas y CS, pero en CS hay una perspectiva adicional: desde la perspectiva de un objeto, los invariantes deben imponerse y establecerse. Desde la perspectiva de un llamador de ese objeto, los invariantes son siempre verdaderos.
meriton

7

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í:

class MyClass {
    string MyProperty { get; set; }
}

De todos modos, el meollo de la implementación real todavía se beneficia mucho de la encapsulación.


Python tiene una característica similar en la que las propiedades pueden tener captadores / establecedores transparentes. Estoy seguro de que hay incluso más idiomas que tienen características similares también.
JAB

1
"La prevalencia de getters / setters IMO es causada por la especificación Java Bean, que lo requiere" Sí, tiene razón. ¿Y quién dijo que debe ser requerido? ¿La razón, por favor?
Gangnus

2
La forma C # es absolutamente la misma que la de Lombok. No veo ninguna diferencia real. Más comodidad práctica, pero obviamente mala en la concepción.
Gangnus

1
@Gangnis La especificación lo requiere. ¿Por qué? Mira mi respuesta para ver un ejemplo concreto de por qué los captadores no son lo mismo que exponer campos: intenta lograr lo mismo sin un captador.
Vince Emigh

@VinceEmigh gangnUs, por favor :-). (Caminata de pandillas, nuez). ¿Y qué pasa con el uso de get / set solo donde lo necesitamos específicamente y la carga masiva en campos privados por reflexión en otros casos?
Gangnus

6

¿Podría alguien explicarme, cuál es el sentido de ocultar todos los campos como privados y luego exponerlos a todos con alguna tecnología adicional? ¿Por qué no usamos simplemente los campos públicos entonces? Siento que hemos recorrido un camino largo y difícil solo para volver al punto de inicio.

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.

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. Pero estas tecnologías aparecieron solo porque teníamos esas numerosas propiedades: campos privados detrás de captadores / setters públicos. 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?

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.

ingrese la descripción de la imagen aquí

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.


2
Los pensamientos más concretos y sabios. +1. Pero, ¿por qué no escribir un grupo de clases, de las cuales cada una cargará / guardará masivamente campos privados en / desde alguna estructura externa? JSON, XML, otro objeto. Podría funcionar con POJO o, tal vez, solo con campos, marcados con una anotación para eso. Ahora estoy pensando en esa alternativa.
Gangnus

@Gangnus Supongo que eso significa una gran cantidad de escritura de código adicional. Si se usa la reflexión, solo se debe escribir un fragmento de código de serialización y puede serializar cualquier clase que siga el patrón específico. C # lo admite a través del [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?
Pharap

@Pharap Lo siento mucho, pero tu comentario es demasiado corto para mí. "aprovechando la reflexión"? ¿Y cuál es el sentido de esconder cosas tan diferentes en una clase? ¿Podrías explicar a qué te refieres?
Gangnus

@Gangnus Me refiero a la reflexión , la capacidad del código para inspeccionar su propia estructura. En Java (y otros lenguajes) puede obtener una lista de todas las funciones que expone una clase y luego usarlas como una forma de extraer los datos de la clase requerida para serializarla, por ejemplo, a XML / JSON. Usar la reflexión que sería un trabajo de una sola vez en lugar de crear constantemente nuevos métodos para salvar estructuras por clase. Aquí hay un ejemplo simple de Java .
Pharap

@Pharap De eso es de lo que estaba hablando. ¿Pero por qué lo llamas "apalancamiento"? Y la alternativa que mencioné en el primer comentario aquí sigue siendo: ¿deberían los campos (no) empaquetados tener anotaciones especiales o no?
Gangnus

5

¿Cuánto podemos hacer sin captadores? ¿Es posible eliminarlos por completo? ¿Qué problemas crea esto? ¿Podríamos incluso prohibir la returnpalabra 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 returncolaborador 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í:

Ingrese la descripción de la imagen aquí

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Si puedes codificar así, entonces usar getters es una opción. No es un mal necesario.


Sí, tales captadores / setters tienen mucho sentido. ¿Pero entiendes que se trata de otra cosa? :-) +1 de todos modos.
Gangnus

1
@Gangnus Gracias. Hay muchas cosas de las que podrías estar hablando. Si al menos quisieras nombrarlos, podría entrar en ellos.
candied_orange

5

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 @Datay 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í! @DataLas 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.


La encapsulación debería permitirnos polimorfismo y seguridad. Gets / sets permiten los abetos, pero están fuertemente contra el segundo.
Gangnus

Si dice que uso un dogma en alguna parte, por favor, diga, ¿cuál de mi texto es dogma? Y no olvides que algunos pensamientos que estoy usando no me gustan ni estoy de acuerdo. Los estoy usando para demostrar una contradicción.
Gangnus

1
"GangNus" :-). Hace una semana trabajé en un proyecto literalmente lleno de captadores y establecedores de llamadas en varias capas. Es absolutamente inseguro y, lo que es peor, apoya a las personas en la codificación insegura. Me alegro de haber cambiado de trabajo lo suficientemente rápido, ya que las personas se acostumbran a esa manera. Entonces, no lo creo, lo veo .
Gangnus

2
@jrh: No sé si hay una explicación formal como tal, pero C # tiene soporte incorporado para propiedades (getters / setters con una sintaxis más agradable) y para tipos anónimos que son tipos con solo propiedades y sin comportamiento. Por lo tanto, los diseñadores de C # se han separado deliberadamente de la metáfora de los mensajes y del principio "decir, no preguntar". El desarrollo de C # desde la versión 2.0 parece haberse inspirado más en lenguajes funcionales que en la pureza tradicional de OO.
JacquesB

1
@jrh: supongo que ahora, pero creo que C # está bastante inspirado en lenguajes funcionales donde los datos y las operaciones están más separados. En arquitecturas distribuidas, la perspectiva también ha cambiado de orientada a mensajes (RPC, SOAP) a orientada a datos (REST). Y han prevalecido las bases de datos que exponen y operan sobre datos (no objetos de "recuadro negro"). En resumen, creo que el enfoque ha cambiado de mensajes entre cajas negras a modelos de datos expuestos
JacquesB

3

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.


¿Podría poner aquí un ejemplo sobre el mal uso de la encapsulación? Eso puede ser interesante. Su ejemplo es más bien contra la falta de encapsulación.
Gangnus

1
@Gangnus agregué algunos ejemplos. La encapsulación inútil es generalmente un uso indebido y también, según mi experiencia, los desarrolladores de API intentan forzarlo a usar una API de cierta manera. No puse un ejemplo para eso porque no tenía uno fácil de presentar. Si encuentro uno, definitivamente lo agregaré. Creo que la mayoría de los comentarios contra la encapsulación son en realidad contra la sintaxis de la encapsulación en lugar de la encapsulación en sí.
BananyaDev

Gracias por la edición Sin embargo, encontré un error menor: "publice" en lugar de "public".
jrh

2

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.


Si bien es una buena idea en teoría, recuerde que hacer que la versión 2 de una API arroje excepciones que la versión 1 no rompió terriblemente a los usuarios de su biblioteca o clase (¡y posiblemente en silencio!); Soy un poco escéptico de que cualquier cambio importante se pueda hacer "detrás de escena" en la mayoría de estas clases de datos.
jrh

Lo siento, no es una explicación. El hecho de que el uso de campos esté prohibido en las interfaces proviene de la idea de encapsulación. Y ahora estamos hablando de eso. Te estás basando en los hechos que se discuten. (Aviso, no estoy hablando a favor o en contra de sus pensamientos, solo sobre que no pueden usarse como argumentos aquí)
Gangnus

@Gangnus La palabra "interfaz" tiene un significado fuera de solo la palabra clave Java: dictionary.com/browse/interface
glennsl

@jrh Ese es un tema completamente diferente. Puede usarlo para argumentar contra la encapsulación en ciertos contextos , pero de ninguna manera invalida los argumentos a favor.
glennsl

1
La antigua encapsulación, sin get / set masivo, nos dio no solo el polimorfismo, sino también la seguridad. Y los conjuntos de masas nos enseñan hacia la mala práctica.
Gangnus

2

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:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

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:

System.out.println(user.getFirstName() + " " + user.getLastName());

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:

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

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.

¿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?

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.


Fantástico. Diría que elevaste la discusión un nivel más alto. Quizás dos. Realmente, pensé que entendía la encapsulación y, a veces, mejor que los libros estándar, y tenía mucho miedo de que prácticamente la estamos perdiendo debido a algunas costumbres y tecnologías aceptadas, y ESO fue el verdadero tema de mi pregunta (tal vez, no formulado en un buena manera), pero me has mostrado lados realmente nuevos de la encapsulación. Definitivamente no soy bueno en la multitarea y me has demostrado lo dañino que puede ser para comprender otros principios básicos.
Gangnus

Pero no puedo marcar su publicación como la respuesta, ya que no se trata de cargar datos en masa hacia / desde objetos, el problema debido a que aparecen plataformas como beans y Lombok. Tal vez, podría elaborar sus pensamientos en esa dirección, por favor, si puede obtener tiempo. Yo todavía tengo que repensar las consecuencias que su pensamiento trae a ese problema. Y no estoy seguro de estar lo suficientemente en forma para eso (recuerde, mal fondo de subprocesos múltiples: - [)
Gangnus

No he usado lombok, pero según tengo entendido, es una forma de implementar un nivel específico de encapsulación (captadores / establecedores en cada campo) con menos tipeo. No cambia la forma ideal de la API para su dominio problemático, es solo una herramienta para escribirla más rápidamente.
Joeri Sebrechts

1

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()y p.y(). Luego, realiza una nueva implementación o subclase que utiliza coordenadas polares, por lo que también puede llamar p.r()y p.theta(), pero su código de cliente que llama p.x()y p.y()sigue siendo válido. La clase misma se convierte transparentemente de la forma polar interna, es decir, lo y()haría ahora return r * sin(theta);. (En este ejemplo, configurar solo x()o y()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í".


2
No es tan agradable como lo ves. Cambiar la representación física interna realmente hace que el objeto sea diferente. Y es malo que se vean de la misma manera, porque tienen cualidades diferentes. El sistema Descartes no tiene puntos especiales, el sistema polar tiene uno.
Gangnus

1
Si no le gusta ese ejemplo específico, seguramente puede pensar en otros que realmente tienen múltiples representaciones equivalentes, o una representación más nueva que se puede convertir ay desde una más antigua.
Davislor

Sí, puede usarlos de manera muy productiva para la presentación. En la interfaz de usuario se puede usar ampliamente. ¿Pero no me obligas a responder mi propia pregunta? :-)
Gangnus

Más eficiente de esa manera, ¿no? :)
Davislor

0

¿Podría alguien explicarme, cuál es el sentido de ocultar todos los campos como privados y luego exponerlos a todos con alguna tecnología adicional?

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?


Estás hablando de las preguntas del estudiante SW de primer año. Ya estamos en otra parte. Nunca diría esto e incluso te daría un plus por respuestas inteligentes, si no fueras tan descortés en la forma de tu respuesta.
Gangnus

0

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.


Bienvenido al mundo de Java. (C #, también). *suspiro*. Pero en cuanto a usar get / set para ocultar la representación interna, tenga cuidado. Se trata solo de formato externo, está bien. Pero si se trata de matemáticas o, peor aún, física u otra cosa real, una representación interna diferente tiene cualidades diferentes. a saber, mi comentario a softwareengineering.stackexchange.com/a/358662/44104
Gangnus el

@Gangnus: Ese ejemplo de punto es muy desafortunado. Hemos tenido bastantes de estos, pero el cálculo siempre fue exacto.
Joshua

-1

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".


En esos lenguajes "modernos", la API parece un acceso de campo, foo.barpero 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étodos foo.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!).
Warbo

1
1. Ocultar la física no siempre es tan bueno. Mire mi comentario a softwareengineering.stackexchange.com/a/358662/44104 . 2. No estoy hablando de lo difícil o simple que es la creación de getter / setter. El C # tiene absolutamente el mismo problema del que estamos hablando. La falta de encapsulación debido a la mala solución de la carga de información masiva. 3. Sí, la solución podría basarse en la sintaxis, pero, por favor, de alguna manera diferente a la que está mencionando. Esos son malos, tenemos aquí una gran página llena de explicaciones. 4. La solución no necesita estar basada en la sintaxis. Se podría hacer en los límites de Java.
Gangnus

-1

¿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í, 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ó.


Sí, las propiedades para la interfaz de usuario tienen un sentido especial, son representaciones públicas y bonitas de los campos. Buen punto.
Gangnus

"Entonces, ¿por qué nosotros / ellos simplemente no usamos métodos reales?" Técnicamente lo hicieron en la versión .Net. Las propiedades son azúcar sintáctica para obtener y establecer funciones.
Pharap

"idiotocracia" ? ¿No quieres decir " idiosincrasia "?
Peter Mortensen

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.