patterns pattern first book java design-patterns api oop

java - pattern - ¿Es una mala práctica hacer que un setter devuelva "esto"?



head first design patterns (27)

¿Es una buena o mala idea hacer que los setters en java devuelvan "this"?

public Employee setName(String name){ this.name = name; return this; }

Este patrón puede ser útil porque entonces puedes encadenar setters así:

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

en lugar de esto:

Employee e = new Employee(); e.setName("Jack Sparrow"); ...and so on... list.add(e);

... pero en cierto modo va en contra de la convención estándar. Supongo que podría valer la pena solo porque puede hacer que el setter haga otra cosa útil. He visto que este patrón usa algunos lugares (por ejemplo, JMock, JPA), pero parece poco común, y solo se usa generalmente para API muy bien definidas donde este patrón se usa en todas partes.

Actualizar:

Lo que he descrito es obviamente válido, pero lo que realmente estoy buscando es algunas reflexiones sobre si esto es generalmente aceptable, y si hay alguna trampa o mejores prácticas relacionadas. Sé sobre el patrón de Builder, pero es un poco más complicado que lo que estoy describiendo, como Josh Bloch lo describe, hay una clase Builder estática asociada para la creación de objetos.


A primera vista: "¡Fantasmal!".

En el pensamiento adicional

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

es en realidad menos propenso a errores que

Employee anEmployee = new Employee(); anEmployee.setName("xxx"); ... list.add(anEmployee);

Muy interesante. Añadiendo idea a la bolsa de herramientas ...


Al menos en teoría , puede dañar los mecanismos de optimización de la JVM estableciendo dependencias falsas entre las llamadas.

Se supone que es azúcar sintáctico, pero de hecho puede crear efectos secundarios en la máquina virtual súper inteligente de Java 43.

Por eso voto no, no lo uses.


Como no devuelve void, ya no es un establecedor de propiedades JavaBean válido. Eso puede ser importante si eres una de las siete personas en el mundo que usan herramientas visuales "Bean Builder", o una de las 17 que usan elementos JSP-bean-setProperty.


De la declaración

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

estoy viendo dos cosas

1) Declaración sin sentido. 2) Falta de legibilidad.


En general, es una buena práctica, pero es posible que las funciones de tipo set utilicen el tipo booleano para determinar si la operación se completó con éxito o no, esa es una forma también. En general, no hay ningún dogma que diga que esto es bueno o en la cama, se debe a la situación, por supuesto.


Es mejor usar otras construcciones de lenguaje si están disponibles. Por ejemplo, en Kotlin, usaría con , aplicaría o dejaría . Si utiliza este enfoque, realmente no necesitará devolver una instancia de su configurador.

Este enfoque permite que su código de cliente sea:

  • Indiferente al tipo de retorno.
  • Más fácil de mantener
  • Evitar los efectos secundarios del compilador.

Aquí hay unos ejemplos.

val employee = Employee().apply { name = "Jack Sparrow" id = 1 foo = "bacon" } val employee = Employee() with(employee) { name = "Jack Sparrow" id = 1 foo = "bacon" } val employee = Employee() employee.let { it.name = "Jack Sparrow" it.id = 1 it.foo = "bacon" }


Este esquema (denominado juego de palabras), denominado "interfaz fluida", se está volviendo muy popular ahora. Es aceptable, pero en realidad no es mi taza de té.


Este patrón particular se llama Encadenamiento de métodos. Enlace de Wikipedia , esto tiene más explicaciones y ejemplos de cómo se hace en varios lenguajes de programación.

PD: Pensé en dejarlo aquí, ya que estaba buscando el nombre específico.


Esto puede ser menos legible

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

o esto

list.add(new Employee() .setName("Jack Sparrow") .setId(1) .setFoo("bacon!"));

Esto es mucho más legible que:

Employee employee = new Employee(); employee.setName("Jack Sparrow") employee.setId(1) employee.setFoo("bacon!")); list.add(employee);


Estoy a favor de los setters que tienen "esto" devuelve. No me importa si no es compatible con frijoles. Para mí, si está bien tener la expresión / declaración "=", entonces los establecedores que devuelven valores están bien.


Estoy de acuerdo con todos los carteles que afirman que esto rompe la especificación de JavaBeans. Hay razones para preservar eso, pero también siento que el uso de este Patrón de Generador (al que se aludió) tiene su lugar; Mientras no se use en todas partes, debería ser aceptable. Para mí, "It''s Place" es donde el punto final es una llamada a un método "build ()".

Por supuesto, hay otras formas de configurar todas estas cosas, pero la ventaja aquí es que evita 1) constructores públicos de muchos parámetros y 2) objetos parcialmente especificados. Aquí, el constructor recopila lo que se necesita y luego llama a su "build ()" al final, lo que puede garantizar que no se construya un objeto parcialmente especificado, ya que a esa operación se le puede dar una visibilidad menos pública. La alternativa sería "objetos de parámetros", pero ese IMHO simplemente hace retroceder el problema un nivel.

No me gustan los constructores de muchos parámetros porque hacen más probable que se pasen muchos argumentos del mismo tipo, lo que puede hacer que sea más fácil pasar los argumentos incorrectos a los parámetros. No me gusta usar muchos configuradores porque el objeto podría usarse antes de que esté completamente configurado. Además, la noción de tener valores por defecto basados ​​en elecciones anteriores se sirve mejor con un método "build ()".

En resumen, creo que es una buena práctica, si se usa correctamente.


Hace mucho tiempo respondo, pero mis dos centavos ... está bien. Me gustaría que esta interfaz fluida se utilizara más a menudo.

Repetir la variable ''fábrica'' no agrega más información a continuación:

ProxyFactory factory = new ProxyFactory(); factory.setSuperclass(Foo.class); factory.setFilter(new MethodFilter() { ...

Esto es más limpio, imho:

ProxyFactory factory = new ProxyFactory() .setSuperclass(Properties.class); .setFilter(new MethodFilter() { ...

Por supuesto, como una de las respuestas ya mencionadas, la API de Java tendría que ser ajustada para hacer esto correctamente en algunas situaciones, como la herencia y las herramientas.


He estado haciendo mis configuradores durante bastante tiempo y el único problema real es con las bibliotecas que se adhieren a los estrictos getPropertyDescriptors para obtener los accesores de beans de bean de lectura / escritura. En esos casos, su "bean" de java no tendrá los escritores que usted esperaría.

Por ejemplo, no lo he probado con seguridad, pero no me sorprendería que Jackson no los reconozca cuando cree los objetos java de json / maps. Espero equivocarme en esto (lo probaré pronto).

De hecho, estoy desarrollando un ORM ligero centrado en SQL y tengo que agregar algunos códigos más allá de getPropertyDescriptors a los configuradores reconocidos que devuelven esto.


No creo que haya nada específicamente incorrecto, es solo una cuestión de estilo. Es útil cuando:

  • Es necesario configurar muchos campos a la vez (incluso en la construcción)
  • usted sabe qué campos debe configurar al momento de escribir el código, y
  • Hay muchas combinaciones diferentes para los campos que desea establecer.

Las alternativas a este método pueden ser:

  1. Un mega constructor (inconveniente: puede pasar muchos valores nulos o valores predeterminados, y es difícil saber qué valor corresponde a qué)
  2. Varios constructores sobrecargados (inconveniente: se vuelve poco manejable una vez que tienes más de unos pocos)
  3. Fábrica / métodos estáticos (inconveniente: igual que los constructores sobrecargados: se vuelve difícil de manejar una vez que hay más que unos pocos)

Si solo va a establecer unas pocas propiedades a la vez, diría que no vale la pena devolver "esto". Ciertamente, se cae si luego decide devolver otra cosa, como un mensaje / indicador de estado / éxito.


No es una mala práctica en absoluto. Pero no es compatible con JavaBeans Spec .

Y hay una gran cantidad de especificación depende de los accesores estándar.

Siempre puedes hacer que coexistan entre sí.

public class Some { public String getValue() { // JavaBeans return value; } public void setValue(final String value) { // JavaBeans this.value = value; } public String value() { // simple return getValue(); } public Some value(final String value) { // fluent/chaining setValue(value); return this; } private String value; }

Ahora podemos usarlos juntos.

new Some().value("some").getValue();

Aquí viene otra versión para objeto inmutable.

public class Some { public static class Builder { public Some build() { return new Some(value); } public Builder value(final String value) { this.value = value; return this; } private String value; } private Some(final String value) { super(); this.value = value; } public String getValue() { return value; } public String value() { return getValue();} private final String value; }

Ahora podemos hacer esto.

new Some.Builder().value("value").build().getValue();


No es una mala práctica. Es una práctica cada vez más común. La mayoría de los idiomas no requieren que trate con el objeto devuelto si no lo desea, por lo que no cambia la sintaxis de uso del configurador "normal", sino que le permite encadenar a los configuradores.

Esto se conoce comúnmente como un patrón de construcción o una interfaz fluida .

También es común en la API de Java:

String s = new StringBuilder().append("testing ").append(1) .append(" 2 ").append(3).toString();


No sé Java pero he hecho esto en C ++. Otras personas han dicho que hace que las líneas sean realmente largas y difíciles de leer, pero lo he hecho así muchas veces:

list.add(new Employee() .setName("Jack Sparrow") .setId(1) .setFoo("bacon!"));

Esto es aún mejor:

list.add( new Employee("Jack Sparrow") .Id(1) .foo("bacon!"));

Al menos, creo. Pero puedes darme una votación y llamarme un programador horrible si lo deseas. Y no sé si está permitido incluso hacer esto en Java.


No solo rompe la convención de getters / setters, también rompe el marco de referencia del método Java 8. MyClass::setMyValue es un BiConsumer<MyClass,MyValue> , y myInstance::setMyValue es un Consumer<MyValue> . Si su setter devuelve this , entonces ya no es una instancia válida de Consumer<MyValue> , sino más bien una Function<MyValue,MyClass> , y causará que cualquier cosa que use las referencias de métodos a esos setters (asumiendo que sean métodos nulos) se rompa .


Para resumir:

  • se llama "interfaz fluida" o "encadenamiento de métodos".
  • Esto no es Java "estándar", aunque sí lo ves más y más en estos días (funciona muy bien en jQuery)
  • viola la especificación de JavaBean, por lo que se romperá con varias herramientas y bibliotecas, especialmente los constructores JSP y Spring.
  • puede evitar algunas optimizaciones que la JVM haría normalmente
  • algunas personas piensan que limpia el código, otras piensan que es "espantoso"

Un par de otros puntos no mencionados:

  • Esto viola el principio de que cada función debe hacer una cosa (y solo una). Puede o no creer en esto, pero en Java creo que funciona bien.

  • Los IDE no los generarán por usted (por defecto).

  • Finalmente, aquí hay un punto de datos del mundo real. He tenido problemas al usar una biblioteca construida de esta manera. El generador de consultas de Hibernate es un ejemplo de esto en una biblioteca existente. Dado que los métodos establecidos * de Query están devolviendo consultas, es imposible saberlo simplemente mirando la firma cómo usarla. Por ejemplo:

    Query setWhatever(String what);

  • Introduce una ambigüedad: ¿el método modifica el objeto actual (su patrón) o, quizás, Query es realmente inmutable (un patrón muy popular y valioso), y el método está devolviendo uno nuevo? Simplemente hace que la biblioteca sea más difícil de usar, y muchos programadores no explotan esta característica. Si los definidores fueran definidores, sería más claro cómo usarlo.


Prefiero usar métodos ''con'' para esto:

public String getFoo() { return foo; } public void setFoo(String foo) { this.foo = foo; } public Employee withFoo(String foo) { setFoo(foo); return this; }

Así:

list.add(new Employee().withName("Jack Sparrow") .withId(1) .withFoo("bacon!"));


Sí, creo que es una buena idea.

Si pudiera agregar algo, ¿qué pasa con este problema?

class People { private String name; public People setName(String name) { this.name = name; return this; } } class Friend extends People { private String nickName; public Friend setNickName(String nickName) { this.nickName = nickName; return this; } }

Esto funcionará:

new Friend().setNickName("Bart").setName("Barthelemy");

¡Esto no será aceptado por Eclipse! :

new Friend().setName("Barthelemy").setNickName("Bart");

Esto se debe a que setName () devuelve un People y no un Friend, y no hay un PeoplesetNickName.

¿Cómo podríamos escribir setters para devolver la clase SELF en lugar del nombre de la clase?

Algo como esto estaría bien (si existiera la palabra clave SELF). ¿Existe esto de todos modos?

class People { private String name; public SELF setName(String name) { this.name = name; return this; } }


Si estoy escribiendo una API, uso "devolver esto" para establecer valores que solo se establecerán una vez. Si tengo otros valores que el usuario debería poder cambiar, en su lugar utilizo un configurador de huecos estándar.

Sin embargo, en mi opinión, es realmente una cuestión de preferencia y encadenadores se ve muy bien.


Si no desea devolver ''this'' desde el configurador pero no quiere usar la segunda opción, puede usar la siguiente sintaxis para establecer propiedades:

list.add(new Employee() {{ setName("Jack Sparrow"); setId(1); setFoo("bacon!"); }});

Como un lado creo que es un poco más limpio en C #:

list.Add(new Employee() { Name = "Jack Sparrow", Id = 1, Foo = "bacon!" });


Si usa la misma convención en toda la aplicación, parece estar bien.

Por otra parte, si una parte existente de su aplicación usa la convención estándar, me apego a ella y agregaría constructores a clases más complicadas

public class NutritionalFacts { private final int sodium; private final int fat; private final int carbo; public int getSodium(){ return sodium; } public int getfat(){ return fat; } public int getCarbo(){ return carbo; } public static class Builder { private int sodium; private int fat; private int carbo; public Builder sodium(int s) { this.sodium = s; return this; } public Builder fat(int f) { this.fat = f; return this; } public Builder carbo(int c) { this.carbo = c; return this; } public NutritionalFacts build() { return new NutritionalFacts(this); } } private NutritionalFacts(Builder b) { this.sodium = b.sodium; this.fat = b.fat; this.carbo = b.carbo; } }


Solía ​​preferir este enfoque, pero he decidido no hacerlo.

Razones:

  • Legibilidad. Hace que el código sea más legible para tener cada setFoo () en una línea separada. Por lo general, lee el código muchas, muchas más veces que la única vez que lo escribe.
  • Efecto secundario: setFoo () solo debe establecer el campo foo, nada más. Devolviendo esto es un extra "QUÉ fue eso".

El patrón Builder que vi no usa la convención setFoo (foo) .setBar (bar) sino más foo (foo) .bar (bar). Quizás por exactamente esas razones.

Es, como siempre, una cuestión de gusto. Simplemente me gusta el enfoque de "menos sorpresas".


Paulo Abrantes ofrece otra forma de hacer que los creadores de JavaBean fluyan: defina una clase de constructor interno para cada JavaBean. Si está utilizando herramientas que se desconciertan con los configuradores que devuelven valores, el patrón de Paulo podría ayudar.


Mal hábito: un setter establece un getter get.

¿Qué hay de declarar explícitamente un método, que lo hace por U?

setPropertyFromParams(array $hashParamList) { ... }