Usar público final en lugar de captadores privados

51

Veo los POJO más inmutables escritos así:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Sin embargo, tiendo a escribirlos así:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Tenga en cuenta que las referencias son finales, por lo que el objeto sigue siendo inmutable. Me permite escribir menos código y permite un acceso más corto (por 5 caracteres: el gety ()).

La única desventaja que puedo ver es que si quieres cambiar la implementación getFoo()en el futuro para hacer algo loco, no puedes. Pero de manera realista, esto nunca sucede porque el Objeto es inmutable; puede verificar durante la creación de instancias, crear copias defensivas inmutables durante la creación de instancias (consulte Guava's, ImmutableListpor ejemplo) y preparar los objetos foou barpara la getllamada.

¿Hay alguna desventaja que me falta?

EDITAR

Supongo que otra desventaja que me falta son las bibliotecas de serialización que utilizan la reflexión sobre los métodos que comienzan con geto is, pero esa es una práctica bastante terrible ...

Cory Kendall
fuente
¿Que demonios? finalno hace que una variable sea el objeto inmutable. Normalmente uso un diseño donde defino finalcampos antes de crear la devolución de llamada para que la devolución de llamada pueda acceder a esos campos. Por supuesto, puede llamar a todos los métodos, incluidos los setXmétodos.
Tomáš Zato
@ TomášZato No existen métodos setX en String, into MyObject. En la primera versión, finales solo para garantizar que otros métodos que no sean el constructor dentro de la clase no intenten bar = 7;, por ejemplo. En la segunda versión, finales necesario evitar que los consumidores haciendo: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall
1
Bueno, su " Tenga en cuenta que las referencias son finales, por lo que Objectsigue siendo inmutable " es engañoso, de esa manera parece que cree que cualquiera final Objectes inmutable, lo que no lo es. Perdón por el malentendido.
Tomáš Zato
3
Si los valores subyacentes fueran mutables, el camino privado + de acceso tampoco lo impediría - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Respuestas:

38

Cuatro desventajas en las que puedo pensar:

  1. Si desea tener una forma de solo lectura y mutable de la misma entidad, un patrón común es tener una Entidad de clase inmutable que exponga solo los accesores con variables miembro protegidas, luego cree una MutableEntity que la extienda y agregue setters. Tu versión lo impide.
  2. El uso de getters y setters se adhiere a la convención JavaBeans. Si desea utilizar su clase como un bean en tecnologías basadas en propiedades, como JSTL o EL, debe exponer a los captadores públicos.
  3. Si alguna vez desea cambiar la implementación para derivar los valores o buscarlos en la base de datos, deberá refactorizar el código del cliente. Un enfoque de acceso / mutador le permite cambiar solo la implementación.
  4. Menos asombro: cuando veo variables de instancia pública, inmediatamente busco quién puede estar mutando y me preocupa que esté abriendo la caja de Pandora porque se pierde la encapsulación. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Dicho esto, tu versión es definitivamente más concisa. Si se tratara de una clase especializada que solo se utiliza dentro de un paquete específico (tal vez el alcance del paquete es una buena idea aquí), entonces podría considerar esto para casos únicos. Pero no expondría las principales API como esta.

Brandon
fuente
3
Grandes respuestas Tengo desacuerdos con algunos de ellos, pero no estoy seguro de que este sitio sea el mejor foro para el debate. En resumen, 1) puedo romper a otros usando la forma mutable donde suponen que es inmutable (tal vez debería agregar final a la clase en mis ejemplos), 2) estoy de acuerdo, 3) argumentaría que ya no es un POJO en ese sentido caso, 4) Estoy de acuerdo por ahora, pero espero que discusiones como esta puedan cambiar lo que es menos sorprendente :).
Cory Kendall
2
5) No puede exponer con seguridad clases / tipos inherentemente mutables, como matrices. La forma segura es devolver una copia de un captador cada vez.
Clockwork-Muse
@ Clockwork-Muse puedes, si asumes que las personas no son idiotas. Al acceder a someObj.thisList.add () es obvio que está modificando el estado de otros objetos. Con someObj.getThisList (). Add () es desconocido. Debe solicitar la documentación o buscar la fuente, pero solo por la declaración del método es imposible saberlo.
kritzikratzi
2
@kritzikratzi: el problema es que no puedes garantizar que tu código no se convierta en idiotas. En algún momento lo hará (¡lo que podría terminar siendo uno mismo!), Y la sorpresa podría ser un gran problema.
Clockwork-Muse el
1
Bueno, dejar que cualquier tipo mutable herede de un tipo inmutable es inherentemente incorrecto. Recuerde que un tipo inmutable da la garantía "No cambio. Nunca".
Deduplicador
26

Deshazte de los captadores / setters también, ¡y estás bien!

Este es un tema muy controvertido entre los programadores de Java.

De todos modos, hay dos situaciones en las que uso variables públicas en lugar de (get) / setters:

  1. público final Para mí esto indica "soy inmutable" mucho mejor que un simple captador. La mayoría de los IDE indicarán el modificador final con una 'F' durante la finalización automática. A diferencia de los getters / setters, donde debes buscar la ausencia de un setXXX.
  2. público no final Me encanta esto para las clases de datos. Solo expongo todos los campos públicamente. Sin captadores, colocadores, constructores. No nada. Menos que un pojo. Para mí, esto inmediatamente indica "mira, soy tonto. Tengo datos, eso es todo. Es TU trabajo poner los datos correctos dentro de mí". Gson / JAXB / etc. manejar estas clases muy bien. Son una dicha para escribir. No hay duda sobre su propósito o capacidades. Y lo más importante: sabes que no hay efectos secundarios cuando cambias una variable. En mi humilde opinión, esto da como resultado modelos de datos muy concisos con pocas ambigüedades, mientras que los captadores y establecedores tienen este gran problema donde a veces ocurre magia dentro de ellos.
kritzikratzi
fuente
55
Es bueno ver algo de cordura de vez en cuando :)
Navin
2
Hasta que llegue el día en que su modelo evolucione y uno de los campos antiguos se pueda derivar de otro. Como desea mantener la compatibilidad con versiones anteriores, el campo anterior debe permanecer, pero en lugar de simplemente cambiar el código getter y setter, tendría que cambiar en cualquier otro lugar donde se utilizara este campo antiguo para garantizar que siga la nueva representación del modelo. Fácil de escribir ... sí, claro ... pesadilla para mantener a largo plazo ... Es por eso que tenemos getter / setter o mejor aún en los accesos de propiedad C #.
Newtopian
9

En palabras simples:

  • Viola la encapsulación para guardar algunas líneas de código. Eso derrota el propósito de OOD.
  • El código del cliente estará unido a los nombres de los miembros de su clase. El acoplamiento es malo. Todo el propósito de OOD es evitar el acoplamiento.
  • También está muy seguro de que su clase nunca tendrá que ser mutable. Las cosas cambian. El cambio es lo único que es constante.
Tulains Córdova
fuente
66
¿Cómo es esto una violación de la encapsulación? Ambas versiones están tan expuestas al mundo exterior como la otra (con acceso de solo lectura). Sin embargo, tiene razón sobre el acoplamiento duro y la inflexibilidad del cambio, no lo argumentaré.
KChaloux
44
@KChaloux Confunde "violación de encapsulación" con "código que no se compila y que tiene que perder el tiempo arreglándolo", primero sucede uno. Lo segundo sucede después. Para que quede claro: la encapsulación ya se viola en el presente. Las entrañas de tu clase ya no están encapsuladas en la actualidad. Pero el código del cliente se romperá en el futuro si la clase cambia en el futuro, porque la encapsulación se rompió en el pasado. Hay dos "interrupciones" diferentes, la encapsulación hoy, el código del cliente mañana, debido a la falta de encapsulación.
Tulains Córdova
8
Técnicamente, cuando usas getters / etc. el código del cliente estará acoplado a los nombres de los métodos. Mire cuántas bibliotecas tienen que incluir versiones antiguas de métodos con una etiqueta / anotación "obsoleta" porque el código anterior todavía depende de ellas (y algunas personas aún pueden usarlas porque les resulta más fácil trabajar con ellas, pero usted no lo es) se supone que debe hacer eso).
JAB
55
Pragmático: ¿con qué frecuencia los programadores realmente refactorizan los nombres de campos privados sin renombrar los captadores / establecedores públicos? Por lo que puedo decir, existe una convención comunitaria para nombrarlos de la misma manera, incluso si esa convención solo puede ser el resultado de herramientas IDE que generan captadores y establecedores a partir de nombres de campo. Teórico: sin embargo, desde una perspectiva de modelado orientado a objetos, todo lo público es parte del contrato público y no un detalle de implementación interna. Entonces, si alguien decide hacer público un campo final, ¿no están diciendo que este es el contrato que prometen cumplir?
Thomas Jung
3
@ user949300 Dime cuáles para que pueda aprender.
Tulains Córdova
6

Una posible desventaja que puedo ver de improviso es que está vinculado a la representación interna de los datos en la clase. Probablemente esto no sea un gran problema, pero si usa los setters, y decide que foo y bar serán devueltos de alguna otra clase definida en otro lugar, las clases que consumen MyObject no deberían cambiar. Solo necesitaría tocar MyObject. Sin embargo, si usa los valores desnudos, entonces tendría que tocar en todas partes el MyObject que usé.

ipaul
fuente