Gerenciando construtores com muitos parâmetros em Java

105

Em alguns de nossos projetos, há uma hierarquia de classes que adiciona mais parâmetros à medida que desce na cadeia. Na parte inferior, algumas das classes podem ter até 30 parâmetros, 28 dos quais estão apenas sendo passados ​​para o superconstrutor.

Reconheço que usar DI automatizado por meio de algo como o Guice seria bom, mas por alguns motivos técnicos, esses projetos específicos são restritos ao Java.

Uma convenção de organizar os argumentos em ordem alfabética por tipo não funciona porque se um tipo for refatorado (o Círculo que você estava passando para o argumento 2 agora é uma Forma), ele pode repentinamente ficar fora de ordem.

Esta pergunta pode ser muito específica e repleta de críticas do tipo "Se esse é o seu problema, você está fazendo errado no nível do design", mas estou apenas procurando por pontos de vista.

Steve Armstrong
fonte

Respostas:

264

O Builder Design Pattern pode ajudar. Considere o seguinte exemplo

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

Isso nos permite escrever código como

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

Se deixarmos de fora um campo obrigatório (presumivelmente o nome é obrigatório), podemos fazer com que o construtor Student lance uma exceção. E nos permite ter argumentos padrão / opcionais sem a necessidade de rastrear qualquer tipo de ordem de argumento, uma vez que qualquer ordem dessas chamadas funcionará igualmente bem.

Eli Courtwright
fonte
10
É claro que, com as importações estáticas, você nunca precisa "ver" esses "construtores". Por exemplo, você poderia ter o nome de métodos estáticos (nome da string) que retorna um construtor e Student (StudentBuilder) que retorna um aluno. Conseqüentemente, Student (name ("Joe"). Age (15) .motto ("Eu me molhei"));
oxbow_lakes
2
@oxbow_lakes: Em seu exemplo, qual classe tem nome de método estático (nome de String)?
user443854
Tecnicamente falando, é possível usar a classe Aluno para construir um novo aluno. Eu adicionei os métodos dentro da classe Student e funcionou muito bem. Dessa forma, não precisei ter outra classe de construtor. Não tenho certeza se isso é desejável. Existe uma razão para usar outra classe (StudentBuilder) para criá-lo?
WVrock de
1
@WVrock: Depende da sua implementação. Como eu disse em minha resposta, fazer isso com a própria classe do aluno poderia potencialmente deixar a classe em um estado semi-inicializado, por exemplo, se você tiver um campo obrigatório que ainda não foi inicializado.
Eli Courtwright
@EliCourtwright Acho que é sobre preferência / design de código. Em vez de fazer o construtor lançar a exceção, fiz o buildStudent()método lançar a exceção.
WVrock
24

Você pode encapsular parâmetros relacionados dentro de um objeto?

por exemplo, se os parâmetros são 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;

então você poderia ter:


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

JeeBee
fonte
8

Bem, usar o padrão do construtor pode ser uma solução.

Mas quando você chega a 20 a 30 parâmetros, acho que há uma grande relação entre os parâmetros. Portanto (como sugerido) agrupá-los em objetos de dados lógicos provavelmente faz mais sentido. Desta forma, o objeto de dados já pode verificar a validade das restrições entre os parâmetros.

Para todos os meus projetos no passado, quando cheguei ao ponto de ter muitos parâmetros (e isso era 8, não 28!), Fui capaz de limpar o código criando um modelo de dados melhor.


fonte
4

Como você está restrito ao Java 1.4, se quiser DI, o Spring seria uma opção muito decente. DI só é útil em locais onde os parâmetros do construtor são serviços ou algo que não varia durante o tempo de execução.

Se você tiver todos esses construtores diferentes devido ao fato de que deseja opções variáveis ​​sobre como construir um objeto, você deve considerar seriamente o uso do padrão Builder.

Guðmundur Bjarni
fonte
Os parâmetros são principalmente serviços, como você mencionou, então DI é o que eu preciso. Acho que o padrão Builder mencionado em algumas outras respostas é exatamente o que eu esperava.
Steve Armstrong,
4

A melhor solução é não ter muitos parâmetros no construtor. Somente os parâmetros realmente necessários no construtor são parâmetros necessários para inicializar corretamente o objeto. Você pode ter construtores com vários parâmetros, mas também pode ter um construtor com apenas os parâmetros mínimos. Os construtores adicionais chamam este construtor simples e depois disso configuradores para definir os outros parâmetros. Desta forma, você pode evitar o problema da cadeia com mais e mais parâmetros, mas também tem alguns construtores de conveniência.

Mnementh
fonte
1

Refatorar para reduzir o número de parâmetros e a profundidade de sua hierarquia de herança é praticamente tudo em que consigo pensar, porque nada vai realmente ajudar a manter os parâmetros de 20 e poucos anos em linha reta. Você apenas terá que atender a todas as ligações enquanto examina a documentação.

Uma coisa que você pode fazer é agrupar alguns parâmetros agrupados logicamente em seus próprios objetos de nível superior, mas isso tem seus próprios problemas.

Aaron Maenpaa
fonte