¿Es una mala práctica hacer que un colocador devuelva “esto”?

¿Es una buena o mala idea hacer que los setters en Java devuelvan “esto”?

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

Este patrón puede ser útil porque entonces puedes encadenar a los setters de esta manera:

 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 va en contra de la convención estándar. Supongo que valdría la pena solo porque puede hacer que ese colocador haga algo más útil. He visto este patrón usado en algunos lugares (por ejemplo, JMock, JPA), pero parece poco común, y solo se usa generalmente para API 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 una reflexión sobre si esto es generalmente aceptable, y si hay algún inconveniente o mejores prácticas relacionadas. Conozco el patrón de Builder, pero está un poco más involucrado que lo que estoy describiendo: como Josh Bloch lo describe, hay una clase de generador estático asociado para la creación de objetos.

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

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

Las alternativas a este método podrían ser:

  1. Un megaconstructor (inconveniente: puede pasar muchos valores nulos o por defecto, y es difícil saber qué valor corresponde a qué)
  2. Varios constructores sobrecargados (inconveniente: se vuelve difícil de manejar una vez que tienes más de unos pocos)
  3. Métodos de fábrica / estáticos (inconveniente: al igual que los constructores sobrecargados – se vuelve difícil de manejar una vez que hay más de unos pocos)

Si solo vas a establecer algunas propiedades a la vez, diría que no vale la pena devolver ‘esto’. Ciertamente se cae si luego decides devolver algo más, como un indicador / mensaje de estado / éxito.

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 maneje el objeto devuelto si no lo desea, de modo que no cambia la syntax de uso del colocador “normal”, sino que le permite encadenar los ajustadores juntos.

Esto se conoce comúnmente como un patrón de generador 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(); 

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!")); 

Para resumir:

  • se llama una “interfaz fluida” o “encadenamiento de métodos”.
  • esto no es Java “estándar”, aunque lo ve más cada vez más (funciona muy bien en jQuery)
  • viola las especificaciones JavaBean, por lo que se romperá con varias herramientas y bibliotecas, especialmente constructores JSP y Spring.
  • puede evitar algunas optimizaciones que la JVM normalmente haría
  • algunas personas piensan que limpia el código, otros piensan que es “espantoso”

Un par de otros puntos no mencionados:

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

  • Los IDE no los generarán por usted (de manera predeterminada).

  • 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. Como los métodos set * de Query son consultas devueltas, es imposible saber con solo mirar la firma cómo usarlas. Por ejemplo:

     Query setWhatever(String what); 
  • Introduce una ambigüedad: ¿el método modifica el objeto actual (su patrón) o, tal vez, 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 progtwigdores no explotan esta característica. Si los setters fueron setters, sería más claro cómo usarlo.

Si no desea devolver 'this' del colocador pero no desea usar la segunda opción, puede usar la siguiente syntax para establecer las 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!" }); 

No solo rompe la convención de getters / setters, sino que también rompe el marco de referencia del método Java 8. MyClass::setMyValue es un BiConsumer , y myInstance::setMyValue es un Consumer . Si su setter lo devuelve, ya no es una instancia válida de Consumer , sino más bien una Function , y hará que cualquier cosa utilizando referencias de métodos para esos setters (suponiendo que sean métodos vacíos) se rompa .

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 invitarme y llamarme un terrible progtwigdor si lo deseas. Y no sé si se te permite hacer esto en Java.

Como no devuelve el vacío, ya no es un establecedor de propiedades de JavaBean válido. Eso podría importar si usted es una de las siete personas en el mundo que usa herramientas visuales de “Bean Builder”, o una de las 17 que usan elementos JSP-bean-setProperty.

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

Y hay una gran cantidad de especificaciones que dependen de los accesadores estándar.

Siempre puedes hacer que coexistan el uno con el otro.

 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 el 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(); 

Este esquema (juego de palabras intencionado), llamado ‘interfaz fluida’, se está volviendo bastante popular ahora. Es aceptable, pero no es realmente mi taza de té.

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

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

Es por eso que voto no, no lo uso.

Estoy a favor de que los setters tengan “esto”. No me importa si no es compatible con frijoles. Para mí, si está bien tener la expresión / statement “=”, entonces los setters que devuelven valores están bien.

Antes prefería 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 veces más que la única vez que lo escribe.
  • Efecto secundario: setFoo () solo debería establecer el campo foo, nada más. Devolviendo esto es un extra “QUE fue eso”.

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

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

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 progtwigción.

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

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

Por otro lado, si una parte existente de su aplicación utiliza una convención estándar, me apegaría 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; } } 

Paulo Abrantes ofrece otra forma de hacer fluidos a los establecedores de JavaBean: definir una clase de constructor interno para cada JavaBean. Si está utilizando herramientas que se desconcertan por los organismos que devuelven los valores, el patrón de Paulo podría ayudar.

A primera vista: “¡espantoso!”.

En 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 …

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

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

Algo así estaría bien (si existiera la palabra clave SELF). ¿Esto existe de todos modos?

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

En general, es una buena práctica, pero es posible que necesite funciones de tipo de conjunto que utilicen el tipo booleano para determinar si la operación se completó satisfactoriamente o no, también de una manera. En general, no hay ningún dogma para decir que esto es bueno o que la cama, viene de la situación, por supuesto.

De la statement

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

estoy viendo dos cosas

1) statement sin sentido. 2) Falta de legibilidad.

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

He estado haciendo mis setters durante bastante tiempo y el único problema real es con las bibliotecas que se adhieren a los estrictos getPropertyDescriptors para obtener acceso a los bean reader / writer bean. En esos casos, su “bean” de Java no tendrá las escrituras que usted esperaría.

Por ejemplo, no lo he probado con certeza, pero no me sorprendería que Jackson no los reconozca como instaladores al crear los objetos Java desde json / maps. Espero estar equivocado en esto (lo probaré pronto).

De hecho, estoy desarrollando un ORM ligero centrado en SQL y tengo que agregar un código beyong getPropertyDescriptors a setters reconocidos que devuelve esto.

Hace mucho tiempo respondiste, pero mis dos centavos … está bien. Ojalá esta interfaz fluida se usara 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 modificarse para hacer esto correctamente en algunas situaciones, como la herencia y las herramientas.

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

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

  • Indiferente al tipo de devolución
  • Más fácil de mantener
  • Evitar los efectos secundarios del comstackdor

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

Si estoy escribiendo una API, utilizo “return this” para establecer valores que solo se establecerán una vez. Si tengo otros valores que el usuario debería poder cambiar, utilizo un setter de vacío estándar en su lugar.

Sin embargo, es realmente una cuestión de preferencia y encadenar setters se ve bastante bien, en mi opinión.

Estoy de acuerdo con todos los carteles que afirman que esto rompe las especificaciones de JavaBeans. Hay razones para preservar eso, pero también siento que el uso de este patrón de construcción (al que se hizo alusión) tiene su lugar; siempre que no se use en todas partes, debería ser aceptable. “It’s Place”, para mí, es donde el punto final es una llamada a un método “build ()”.

Hay otras formas de establecer todas estas cosas, por supuesto, pero la ventaja aquí es que evita 1) constructores públicos de muchos parámetros y 2) objetos parcialmente especificados. Aquí, el constructor recostackrá lo que necesita y luego llamará a “build ()” al final, lo que puede garantizar que no se construya un objeto parcialmente especificado, ya que a esa operación se le puede otorgar una visibilidad menor que la pública. La alternativa sería “objetos de parámetros”, pero eso en mi humilde opinión simplemente devuelve el problema a un nivel.

No me gustan los constructores de muchos parámetros porque hacen que sea 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 setters porque el objeto podría usarse antes de que esté completamente configurado. Además, la noción de tener valores predeterminados basados ​​en elecciones previas se sirve mejor con un método “build ()”.

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

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