Administrar constructores con muchos parámetros en Java

105

En algunos de nuestros proyectos, hay una jerarquía de clases que agrega más parámetros a medida que avanza en la cadena. En la parte inferior, algunas de las clases pueden tener hasta 30 parámetros, 28 de los cuales simplemente se pasan al superconstructor.

Reconozco que usar DI automatizado a través de algo como Guice sería bueno, pero debido a algunas razones técnicas, estos proyectos específicos están restringidos a Java.

Una convención de organizar los argumentos alfabéticamente por tipo no funciona porque si se refactoriza un tipo (el Círculo que estaba pasando para el argumento 2 ahora es una Forma), de repente puede estar fuera de orden.

Esta pregunta podría ser demasiado específica y estar plagada de críticas de "Si ese es tu problema, lo estás haciendo mal a nivel de diseño", pero solo estoy buscando puntos de vista.

Steve Armstrong
fuente

Respuestas:

264

El patrón de diseño del constructor podría ayudar. Considere el siguiente ejemplo

public class StudentBuilder
{
    private String _name;
    private int _age = 14;      // this has a default
    private String _motto = ""; // most students don't have one

    public StudentBuilder() { }

    public Student buildStudent()
    {
        return new Student(_name, _age, _motto);
    }

    public StudentBuilder name(String _name)
    {
        this._name = _name;
        return this;
    }

    public StudentBuilder age(int _age)
    {
        this._age = _age;
        return this;
    }

    public StudentBuilder motto(String _motto)
    {
        this._motto = _motto;
        return this;
    }
}

Esto nos permite escribir código como

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
                 .name("Spicoli")
                 .age(16)
                 .motto("Aloha, Mr Hand")
                 .buildStudent();

Si dejamos fuera un campo obligatorio (presumiblemente, el nombre es obligatorio), entonces podemos hacer que el constructor Student genere una excepción. Y nos permite tener argumentos predeterminados / opcionales sin necesidad de realizar un seguimiento de ningún tipo de orden de argumentos, ya que cualquier orden de esas llamadas funcionará igualmente bien.

Eli Courtwright
fuente
10
Por supuesto, con las importaciones estáticas nunca tendrá que "ver" a estos "constructores" en absoluto. Por ejemplo, podría tener un nombre de método estático (nombre de cadena) que devuelve un constructor y un estudiante (StudentBuilder) que devuelve un alumno. Por lo tanto, Estudiante (nombre ("Joe"). Edad (15) .motto ("Me he orinado"));
oxbow_lakes
2
@oxbow_lakes: En su ejemplo, ¿qué clase tiene un nombre de método estático (nombre de cadena)?
user443854
Técnicamente hablando, es posible utilizar la clase de estudiante para construir un nuevo estudiante. Agregué los métodos dentro de la clase Student y funcionó bien. De esta manera no necesitaba tener otra clase de constructor. Sin embargo, no estoy seguro de si esto es deseable. ¿Hay alguna razón para usar otra clase (StudentBuilder) para construirlo?
WVrock
1
@WVrock: Depende de su implementación. Como digo en mi respuesta, hacer esto con la clase del estudiante en sí podría dejar la clase en un estado medio inicializado, por ejemplo, si tiene un campo obligatorio que aún no se ha inicializado.
Eli Courtwright
@EliCourtwright Supongo que se trata de preferencia / diseño de código. En lugar de hacer que el constructor lanzara la excepción, hice que el buildStudent()método lanzara la excepción.
WVrock
24

¿Puede encapsular parámetros relacionados dentro de un objeto?

por ejemplo, si los parámetros son como


MyClass(String house, String street, String town, String postcode, String country, int foo, double bar) {
  super(String house, String street, String town, String postcode, String country);
  this.foo = foo;
  this.bar = bar;

entonces podrías tener en su lugar:


MyClass(Address homeAddress, int foo, double bar) {
  super(homeAddress);
  this.foo = foo;
  this.bar = bar;
}

JeeBee
fuente
8

Bueno, usar el patrón del constructor podría ser una solución.

Pero una vez que llega a 20 a 30 parámetros, supongo que existe una alta relación entre los parámetros. Entonces (como se sugiere) envolverlos en objetos de datos lógicamente cuerdos probablemente tenga más sentido. De esta manera, el objeto de datos ya puede verificar la validez de las restricciones entre los parámetros.

Para todos mis proyectos en el pasado, una vez que llegué al punto de tener demasiados parámetros (¡y eso fue 8, no 28!) Pude desinfectar el código creando un mejor modelo de datos.


fuente
4

Como está limitado a Java 1.4, si desea DI, entonces Spring sería una opción muy decente. DI solo es útil en lugares donde los parámetros del constructor son servicios o algo que no varía durante el tiempo de ejecución.

Si tiene todos esos constructores diferentes debido al hecho de que desea opciones variables sobre cómo construir un objeto, debería considerar seriamente el uso del patrón Builder.

Guðmundur Bjarni
fuente
Los parámetros son principalmente servicios como mencionaste, por lo que DI es lo que necesitaría. Creo que el patrón Builder mencionado en un par de otras respuestas es exactamente lo que esperaba.
Steve Armstrong
4

La mejor solución es no tener demasiados parámetros en el constructor. Solo los parámetros realmente necesarios en el constructor son parámetros que se necesitan para inicializar correctamente el objeto. Puede tener constructores con múltiples parámetros, pero también tener un constructor con solo los parámetros mínimos. Los constructores adicionales llaman a este constructor simple y, después, establece los otros parámetros. De esta manera puede evitar el problema de la cadena con más y más parámetros, pero también tiene algunos constructores de conveniencia.

Mnementh
fuente
1

Refactorizar para reducir la cantidad de parámetros y la profundidad de su jerarquía de herencia es prácticamente todo en lo que puedo pensar porque, en realidad, nada ayudará a mantener rectos los parámetros de 20 y tantos. Solo tendrá que atender cada llamada mientras mira la documentación.

Una cosa que podría hacer es agrupar algunos parámetros agrupados lógicamente en su propio objeto de nivel superior, pero eso tiene sus propios problemas.

Aaron Maenpaa
fuente