¿Es una mala práctica hacer que un armador devuelva "esto"?


¿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 como este:

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 lo convencional. Supongo que podría valer la pena solo porque puede hacer que el armador haga algo más útil. He visto que este patrón se usa en 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.

Actualización:

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

Author: Ken Liu, 2009-08-28

27 answers

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

  • Necesita establecer 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 mega constructor (inconveniente: puede pasar un montón de valores nulos o predeterminados, y se hace difícil saber qué valor corresponde a qué)
  2. Varios constructores sobrecargados (inconveniente: se vuelve difícil de manejar una vez que tiene más de unos pocos)
  3. Métodos de fábrica/estáticos (inconveniente: igual que los constructores sobrecargados - se vuelve difícil de manejar una vez que hay más de 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 decides devolver algo más, como un estado/indicador de éxito/mensaje.

 62
Author: Tom Clift,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 04:36:42

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 manejes el objeto devuelto si no quieres, por lo que no cambia la sintaxis de uso del setter "normal", sino que te permite encadenar los setters.

Esto se llama comúnmente un patrón de constructor 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();
 93
Author: cletus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 04:25:52

Prefiero usar métodos 'with'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!"));
 73
Author: qualidafial,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-03 15:28:14

Para resumir:

  • se llama una "interfaz fluida", o "encadenamiento de métodos".
  • esto no es Java" estándar", aunque lo ves más y más en estos días (funciona muy bien en jQuery)
  • viola la especificación JavaBean, por lo que se romperá con varias herramientas y bibliotecas, especialmente JSP builders y Spring.
  • puede evitar algunas optimizaciones que la JVM haría normalmente
  • 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. Puedes o no creer en esto, pero en Java creo que funciona bien.

  • Los IDE no van a generarlos (por defecto).

  • Finalmente, aquí hay un punto de datos del mundo real. He tenido problemas usando una biblioteca construida como esta. El generador de consultas de Hibernate es un ejemplo de esto en un biblioteca existente. Dado que los métodos set* de Query devuelven consultas, es imposible saber con solo mirar 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, tal vez la consulta 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 setters fueran setters, sería más claro cómo usarlo.

 70
Author: ndp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-27 11:27:03

Si no desea devolver 'this' desde el configurador, pero no desea 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 aparte creo que es ligeramente más limpio en C#:

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});
 23
Author: Luke Quinane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-02 11:46:55

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 tiene su setter return 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 referencias de métodos a esos setters (suponiendo que sean métodos void) se rompa.

 8
Author: Steve K,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-13 01:40:23

No conozco Java pero he hecho esto en C++. Otras personas han dicho que hace que las líneas sean muy largas y muy 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 rechazarme y llamarme programador horrible si lo deseas. Y no se si puedes hacer esto en Java.

 7
Author: Carson Myers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 05:05:54

Debido a que no devuelve void, ya no es un configurador de propiedades JavaBean válido. Eso podría importar 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.

 6
Author: Ken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 04:57:56

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

Y hay mucha especificación depende de esos accesores estándar.

Siempre se puede 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();
 5
Author: Jin Kwon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-18 01:40:53

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

 4
Author: Noon Silk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 04:25:41

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 de Java 43 súper inteligente.

Por eso voto no, no lo uses.

 4
Author: Marian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-22 18:24:19

Estoy a favor de que los setters tengan "this" returns. No me importa si no es compatible con Beans. Para mí, si está bien tener la expresión/sentencia"=", entonces los setters que devuelven valores están bien.

 3
Author: Monty Hall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-02 14:45:19

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

Razones:

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

El patrón de Constructor que vi no use el setFoo(foo).setBar (bar) convención pero más foo (foo).bar (bar). Tal vez por exactamente esas razones.

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

 2
Author: Thorbjørn Ravn Andersen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 05:26:55

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

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

 2
Author: capt.swag,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-27 09:34:40

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

En la otra mano, si la parte existente de su aplicación utiliza la convención estándar, me adherirí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;
    }
}
 2
Author: Marcin Szymczak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-27 07:21:42

Paulo Abrantes ofrece otra manera de hacer JavaBean incubadoras fluidez: definir un interior constructor de la clase para cada JavaBean. Si estás usando herramientas que se confunden con setters que devuelven valores, el patrón de Paulo podría ayudar.

 2
Author: Jim Ferrans,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-14 00:04:38

A primera vista: "Espantoso!".

Pensándolo mejor

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ñadir idea bolso ...

 1
Author: djna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-28 04:34:39

Sí, creo que es una buena idea.

Si pudiera añadir 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 SELF class en lugar del nombre de la clase ?

Algo como esto 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;
    }
}
 1
Author: Baptiste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-04 09:45:01

En general es una buena práctica, pero es posible que necesite que las funciones de tipo set usen el tipo booleano para determinar si la operación se completó con éxito o no, esa es una manera también. En general, no hay dogma para decir que esto es bueno o cama, viene de la situación, por supuesto.

 1
Author: Narek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-15 17:08:17

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.

 0
Author: Madhu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-11-01 05:51:05

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); 
 0
Author: Scott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-27 11:27:52

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 los accessors bean reader/writer bean. En esos casos, tu java "bean" no tendrá los escritores que esperarías.

Por ejemplo, no lo he probado con seguridad, pero no me sorprendería que Jackson no los reconozca como configuradores al crear objetos java a partir de json/maps. Espero estar equivocado en este caso (voy a probar it soon).

De hecho, estoy desarrollando un ligero SQL centric OR y tengo que agregar algún código beyong getPropertyDescriptors a los configuradores reconocidos que devuelve esto.

 0
Author: Jeremy Chone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-05 18:55:29

Respuesta de hace mucho tiempo, pero mis dos centavos ... Está bien. Desearía que esta interfaz fluida se usara más a menudo.

Repetir la variable 'factory' no añade más información a continuación:

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

Esto es más limpio, en mi humilde opinión:

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 para algunas situaciones, como la herencia y las herramientas.

 0
Author: Josef.B,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-18 21:20:26

Es mejor usar otras construcciones de lenguaje si están disponibles. Por ejemplo, en Kotlin, tendría que usar con, aplicar, o vamos a. Si usa este enfoque, realmente no necesitará para devolver una instancia de su creador.

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

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

Aquí hay algunos 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"
}
 0
Author: Steven Spungin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-15 14:16:15

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 configurador de vacío estándar en su lugar.

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

 0
Author: Kaiser Keister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-01 05:56:48

Estoy de acuerdo con todos los carteles que afirman que esto rompe la especificación JavaBeans. Hay razones para preservar eso, pero también siento que el uso de este Patrón Constructor (al que se aludió) tiene su lugar; mientras 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 configurar 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 recopila lo que se necesita y luego llama a su "build ()" al final, lo que puede garantizar que un objeto parcialmente especificado no se construya, ya que esa operación puede tener una visibilidad menos que pública. La alternativa sería "objetos de parámetro", pero eso en mi humilde opinión solo empuja el problema hacia atrás un nivel.

No me gustan los constructores de muchos parámetros porque hacen más probable que se pasen muchos argumentos del mismo tipo, 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 predeterminados 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.

 0
Author: Javaneer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-18 14:28:42

Mal hábito: un setter set un getter get

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

setPropertyFromParams(array $hashParamList) { ... }
 -4
Author: Ulrich Tevi Horus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-07 16:09:56