Qual é a melhor maneira de refatorar um método que tem muitos (6+) parâmetros?

102

Ocasionalmente, encontro métodos com um número desconfortável de parâmetros. Na maioria das vezes, eles parecem ser construtores. Parece que deveria haver uma maneira melhor, mas não consigo ver qual é.

return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)

Pensei em usar structs para representar a lista de parâmetros, mas isso parece apenas deslocar o problema de um lugar para outro e criar outro tipo no processo.

ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);

Portanto, isso não parece uma melhoria. Então, qual é a melhor abordagem?

recursivo
fonte
Você disse "estrutura". Esse termo tem conotações diferentes em diferentes linguagens de programação. O que você pretende significar?
Jay Bazuzi
1
Se você está procurando uma linguagem específica para eliminar a ambigüidade, use C #. Mas basicamente, apenas uma bolsa de propriedades simples. Possui diferentes propriedades nomeadas com diferentes tipos. Pode ser definido como uma classe, tabela de hash, estrutura ou qualquer outra coisa.
recursivo de
Este artigo oferece uma boa visão sobre o assunto. Específico do Javascript, mas os princípios podem ser reaplicados a outras linguagens.
lala de

Respostas:

94

A melhor maneira seria encontrar maneiras de agrupar os argumentos. Isso pressupõe, e realmente só funciona se, você acabar com vários "agrupamentos" de argumentos.

Por exemplo, se você está passando a especificação de um retângulo, pode passar x, y, largura e altura ou pode apenas passar um objeto retângulo que contém x, y, largura e altura.

Procure coisas assim ao refatorar para limpá-lo um pouco. Se os argumentos realmente não podem ser combinados, comece a ver se você violou o Princípio da Responsabilidade Única.

Matthew Brubaker
fonte
4
Boa ideia, mas mau exemplo; o construtor para o retângulo teria que ter 4 argumentos. Isso faria mais sentido se o método estivesse esperando 2 conjuntos de coordenadas / dimensões retangulares. Então você poderia passar 2 retângulos em vez de x1, x2, y1, y2 ...
Programador Outlaw
2
Justo. Como eu disse, realmente só faz sentido se você terminar com vários agrupamentos lógicos.
Matthew Brubaker
23
+1: To Single Responsibility, é um dos poucos comentários em todas as respostas que está realmente abordando o verdadeiro problema. O objeto realmente precisa de 7 valores independentes para formar sua identidade.
AnthonyWJones
6
@AnthonyWJones Eu discordo. Os dados para as condições climáticas atuais podem ter muito mais valores independentes para formar sua identidade.
funct7
107

Vou supor que você quer dizer C # . Algumas dessas coisas também se aplicam a outros idiomas.

Você tem várias opções:

mudar de construtor para configuradores de propriedade . Isso pode tornar o código mais legível, porque é óbvio para o leitor qual valor corresponde a quais parâmetros. A sintaxe do Object Initializer torna isso bonito. Também é simples de implementar, pois você pode apenas usar as propriedades geradas automaticamente e pular a escrita dos construtores.

class C
{
    public string S { get; set; }
    public int I { get; set; }
}

new C { S = "hi", I = 3 };

No entanto, você perde a imutabilidade e a capacidade de garantir que os valores necessários sejam definidos antes de usar o objeto em tempo de compilação.

Padrão de construtor .

Pense na relação entre stringe StringBuilder. Você pode conseguir isso para suas próprias aulas. Gosto de implementá-lo como uma classe aninhada, portanto, classe Ctem classe relacionada C.Builder. Também gosto de uma interface fluente no construtor. Feito da maneira certa, você pode obter uma sintaxe como esta:

C c = new C.Builder()
    .SetX(4)    // SetX is the fluent equivalent to a property setter
    .SetY("hello")
    .ToC();     // ToC is the builder pattern analog to ToString()

// Modify without breaking immutability
c = c.ToBuilder().SetX(2).ToC();

// Still useful to have a traditional ctor:
c = new C(1, "...");

// And object initializer syntax is still available:
c = new C.Builder { X = 4, Y = "boing" }.ToC();

Eu tenho um script do PowerShell que me permite gerar o código do construtor para fazer tudo isso, onde a entrada se parece com:

class C {
    field I X
    field string Y
}

Assim, posso gerar em tempo de compilação. partialclasses me permitem estender a classe principal e o construtor sem modificar o código gerado.

Refatoração "Introduce Parameter Object" . Veja o Catálogo de Refatoração . A ideia é que você pegue alguns dos parâmetros que está passando e os coloque em um novo tipo e, em seguida, passe uma instância desse tipo. Se você fizer isso sem pensar, você acabará de volta ao ponto de partida:

new C(a, b, c, d);

torna-se

new C(new D(a, b, c, d));

No entanto, essa abordagem tem o maior potencial para causar um impacto positivo em seu código. Portanto, continue seguindo estas etapas:

  1. Procure subconjuntos de parâmetros que façam sentido juntos. Apenas agrupar descuidadamente todos os parâmetros de uma função não é muito útil; o objetivo é ter agrupamentos que façam sentido. Você saberá que acertou quando o nome do novo tipo for óbvio.

  2. Procure outros lugares onde esses valores são usados ​​juntos e use o novo tipo lá também. Provavelmente, quando você encontrar um bom tipo novo para um conjunto de valores que já usa em todos os lugares, esse novo tipo fará sentido em todos esses lugares também.

  3. Procure a funcionalidade que está no código existente, mas pertence ao novo tipo.

Por exemplo, talvez você veja algum código parecido com:

bool SpeedIsAcceptable(int minSpeed, int maxSpeed, int currentSpeed)
{
    return currentSpeed >= minSpeed & currentSpeed < maxSpeed;
}

Você poderia tomar as minSpeede maxSpeedparâmetros e colocá-los em um novo tipo:

class SpeedRange
{
   public int Min;
   public int Max;
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return currentSpeed >= sr.Min & currentSpeed < sr.Max;
}

Isso é melhor, mas para realmente aproveitar as vantagens do novo tipo, mova as comparações para o novo tipo:

class SpeedRange
{
   public int Min;
   public int Max;

   bool Contains(int speed)
   {
       return speed >= min & speed < Max;
   }
}

bool SpeedIsAcceptable(SpeedRange sr, int currentSpeed)
{
    return sr.Contains(currentSpeed);
}

E agora estamos chegando a algum lugar: a implementação de SpeedIsAcceptable()now diz o que você quer dizer e você tem uma classe útil e reutilizável. (O próximo passo óbvio é fazer SpeedRangeem Range<Speed>.)

Como você pode ver, Introduce Parameter Object foi um bom começo, mas seu valor real foi que nos ajudou a descobrir um tipo útil que estava faltando em nosso modelo.

Jay Bazuzi
fonte
4
Eu sugeriria tentar "Introduzir Objeto de Parâmetro" primeiro, e apenas recorrer às outras opções se você não encontrar um bom objeto de parâmetro para criar.
Douglas Leeder
4
excelente resposta. se você mencionou a explicação de refatoração antes dos açúcares sintáticos c #, isso teria sido votado como IMHO superior.
rpattabi
10
Ooh! +1 para "Você saberá que acertou quando o nome do novo tipo for óbvio."
Sean McMillan
20

Se for um construtor, especialmente se houver várias variantes sobrecarregadas, você deve olhar para o padrão Builder:

Foo foo = new Foo()
          .configBar(anything)
          .configBaz(something, somethingElse)
          // and so on

Se for um método normal, você deve pensar sobre as relações entre os valores que estão sendo passados ​​e talvez criar um Transfer Object.

kdgregory
fonte
Excelente resposta. Talvez até mais relevante do que a resposta "coloque os parâmetros em uma classe" que todos (inclusive eu) deram.
Wouter Lievens
1
Provavelmente é uma má ideia tornar sua classe mutável apenas para evitar passar muitos parâmetros para o construtor.
Outlaw Programmer
@outlaw - se a mutabilidade for uma preocupação, você pode implementar facilmente a semântica "run once". No entanto, um grande número de parâmetros de ctor geralmente indica uma necessidade de configuração (ou, como outros notaram, uma classe tentando fazer muitas coisas). (cont)
kdgregory
Embora você possa externalizar a configuração, em muitos casos isso é desnecessário, especialmente se for orientado pelo estado do programa ou for padrão para um determinado programa (pense em analisadores XML, que podem reconhecer o namespace, validar com ferramentas diferentes etc.).
kdgregory
Gosto do padrão de construtor, mas separo meus tipos de construtor imutáveis ​​e mutáveis, como string / StringBuilder, mas uso classes aninhadas: Foo / Foo.Builder. Eu tenho um script PowerShell para gerar o código para fazer isso para classes de dados simples.
Jay Bazuzi
10

A resposta clássica para isso é usar uma classe para encapsular alguns ou todos os parâmetros. Em teoria, isso parece ótimo, mas sou o tipo de cara que cria classes para conceitos que têm significado no domínio, então nem sempre é fácil aplicar esse conselho.

Por exemplo, em vez de:

driver.connect(host, user, pass)

Você poderia usar

config = new Configuration()
config.setHost(host)
config.setUser(user)
config.setPass(pass)
driver.connect(config)

YMMV

Wouter Lievens
fonte
5
Eu definitivamente gostaria mais do primeiro pedaço de código. Concordo que existe um certo limite, acima do qual o número de parâmetros torna-se feio, mas para o meu gosto, 3 seriam aceitáveis.
blabla999
10

Este é citado do livro de Fowler e Beck: "Refactoring"

Lista Longa de Parâmetros

Em nossos primeiros dias de programação, fomos ensinados a passar como parâmetros tudo o que é necessário para uma rotina. Isso era compreensível porque a alternativa eram os dados globais, e os dados globais são nocivos e geralmente dolorosos. Os objetos mudam essa situação porque, se você não tiver algo de que precisa, sempre pode pedir a outro objeto que pegue para você. Assim, com os objetos, você não passa tudo que o método precisa; em vez disso, você passa o suficiente para que o método possa obter tudo o que precisa. Muito do que um método precisa está disponível na classe host do método. Em programas orientados a objetos, as listas de parâmetros tendem a ser muito menores do que em programas tradicionais. Isso é bom porque longas listas de parâmetros são difíceis de entender, porque se tornam inconsistentes e difíceis de usar, e porque você está sempre os alterando à medida que precisa de mais dados. A maioria das alterações é removida passando objetos porque é muito mais provável que você precise fazer apenas algumas solicitações para obter um novo dado. Use Substituir Parâmetro por Método quando você puder obter os dados em um parâmetro fazendo uma solicitação de um objeto que você já conhece. Este objeto pode ser um campo ou outro parâmetro. Use Preserve Whole Object para pegar um monte de dados coletados de um objeto e substituí-los pelo próprio objeto. Se você tiver vários itens de dados sem nenhum objeto lógico, use Introduzir objeto de parâmetro. Há uma exceção importante para fazer essas alterações. Isso ocorre quando você não deseja explicitamente criar uma dependência do objeto chamado para o objeto maior. Nesses casos, descompactar os dados e enviá-los como parâmetros é razoável, mas preste atenção à dor envolvida. Se a lista de parâmetros for muito longa ou mudar com muita frequência, você precisa repensar sua estrutura de dependência.

Youssef
fonte
7

Não quero soar como um espertinho, mas você também deve verificar para ter certeza de que os dados que você está passando realmente devem ser repassados: Passar coisas para um construtor (ou método para esse assunto) cheira um pouco a pouca ênfase no comportamento de um objeto.

Não me interpretem mal: métodos e construtores vai ter um monte de parâmetros, às vezes. Mas, quando encontrado, tente considerar o encapsulamento de dados com comportamento .

Esse tipo de cheiro (já que estamos falando de refatoração, essa palavra horrível parece apropriada ...) também pode ser detectado para objetos que têm muitas (leia-se: qualquer) propriedades ou getters / setters.

Daren Thomas
fonte
7

Quando vejo longas listas de parâmetros, minha primeira pergunta é se esta função ou objeto está fazendo muito. Considerar:

EverythingInTheWorld earth=new EverythingInTheWorld(firstCustomerId,
  lastCustomerId,
  orderNumber, productCode, lastFileUpdateDate,
  employeeOfTheMonthWinnerForLastMarch,
  yearMyHometownWasIncorporated, greatGrandmothersBloodType,
  planetName, planetSize, percentWater, ... etc ...);

Claro que este exemplo é deliberadamente ridículo, mas eu vi muitos programas reais com exemplos apenas um pouco menos ridículos, onde uma classe é usada para conter muitas coisas mal relacionadas ou não relacionadas, aparentemente apenas porque o mesmo programa de chamada precisa de ambos ou porque o o programador pensou em ambos ao mesmo tempo. Às vezes, a solução fácil é apenas dividir a classe em várias partes, cada uma delas fazendo sua própria função.

Um pouco mais complicado é quando uma classe realmente precisa lidar com várias coisas lógicas, como um pedido do cliente e informações gerais sobre o cliente. Nesses casos, crie uma classe para o cliente e uma classe para o pedido, e deixe que conversem entre si quando necessário. Então, em vez de:

 Order order=new Order(customerName, customerAddress, customerCity,
   customerState, customerZip,
   orderNumber, orderType, orderDate, deliveryDate);

Poderíamos ter:

Customer customer=new Customer(customerName, customerAddress,
  customerCity, customerState, customerZip);
Order order=new Order(customer, orderNumber, orderType, orderDate, deliveryDate);

Embora eu prefira funções que usem apenas 1 ou 2 ou 3 parâmetros, às vezes temos que aceitar que, realisticamente, essa função exige muito e que o número por si só não cria complexidade. Por exemplo:

Employee employee=new Employee(employeeId, firstName, lastName,
  socialSecurityNumber,
  address, city, state, zip);

Sim, é um monte de campos, mas provavelmente tudo o que vamos fazer com eles é salvá-los em um registro de banco de dados ou jogá-los em uma tela ou algo parecido. Não há realmente muito processamento aqui.

Quando minhas listas de parâmetros ficam longas, prefiro dar aos campos diferentes tipos de dados. Como quando vejo uma função como:

void updateCustomer(String type, String status,
  int lastOrderNumber, int pastDue, int deliveryCode, int birthYear,
  int addressCode,
  boolean newCustomer, boolean taxExempt, boolean creditWatch,
  boolean foo, boolean bar);

E então eu vejo que é chamado com:

updateCustomer("A", "M", 42, 3, 1492, 1969, -7, true, false, false, true, false);

Eu fico preocupado. Olhando para a chamada, não está claro o que todos esses números, códigos e sinalizadores crípticos significam. Isso é apenas pedir erros. Um programador pode facilmente ficar confuso sobre a ordem dos parâmetros e acidentalmente trocar dois, e se eles forem do mesmo tipo de dados, o compilador simplesmente os aceitaria. Eu prefiro ter uma assinatura onde todas essas coisas são enums, então uma chamada passa em coisas como Type.ACTIVE em vez de "A" e CreditWatch.NO em vez de "false", etc.

Jay
fonte
5

Se alguns dos parâmetros do construtor forem opcionais, faz sentido usar um construtor, que obteria os parâmetros necessários no construtor e teria métodos para os opcionais, retornando o construtor, a serem usados ​​desta forma:

return new Shniz.Builder(foo, bar).baz(baz).quux(quux).build();

Os detalhes disso são descritos em Effective Java, 2nd Ed., P. 11. Para argumentos de método, o mesmo livro (p. 189) descreve três abordagens para encurtar listas de parâmetros:

  • Divida o método em vários métodos que usam menos argumentos
  • Crie classes de membros auxiliares estáticos para representar grupos de parâmetros, ou seja, passe um em DinoDonkeyvez dedino edonkey
  • Se os parâmetros forem opcionais, o construtor acima pode ser adotado para métodos, definindo um objeto para todos os parâmetros, definindo os necessários e, em seguida, chamando algum método execute nele
Fabian Steeg
fonte
4

Eu usaria o construtor padrão e os settors de propriedade. C # 3.0 tem uma ótima sintaxe para fazer isso de forma automática.

return new Shniz { Foo = foo,
                   Bar = bar,
                   Baz = baz,
                   Quuz = quux,
                   Fred = fred,
                   Wilma = wilma,
                   Barney = barney,
                   Dino = dino,
                   Donkey = donkey
                 };

A melhoria do código vem simplificando o construtor e não tendo que oferecer suporte a vários métodos para oferecer suporte a várias combinações. A sintaxe de "chamada" ainda é um pouco "prolixa", mas não é realmente pior do que chamar os definidores de propriedade manualmente.

Tvanfosson
fonte
2
Isso permitiria a existência de um objeto t new Shniz (). Uma boa implementação OO buscaria minimizar a possibilidade de objetos existirem em estado incompleto.
AnthonyWJones
Em geral, qualquer linguagem com uma sintaxe de hash / dicionário nativa vem com um substituto adequado para parâmetros nomeados (que são ótimos e muitas vezes o que essas situações exigem, mas por alguma razão a única linguagem popular que os suporta é a pior do planeta) .
caos
4

Você não forneceu informações suficientes para justificar uma boa resposta. Uma longa lista de parâmetros não é inerentemente ruim.

Shniz (foo, bar, baz, quux, fred, wilma, barney, dino, burro)

pode ser interpretado como:

void Shniz(int foo, int bar, int baz, int quux, int fred, 
           int wilma, int barney, int dino, int donkey) { ...

Nesse caso, é muito melhor criar uma classe para encapsular os parâmetros, porque você dá significado aos diferentes parâmetros de uma forma que o compilador pode verificar e também torna o código mais fácil de ler visualmente. Também torna mais fácil ler e refatorar posteriormente.

// old way
Shniz(1,2,3,2,3,2,1,2);
Shniz(1,2,2,3,3,2,1,2); 

//versus
ShnizParam p = new ShnizParam { Foo = 1, Bar = 2, Baz = 3 };
Shniz(p);

Alternativamente, se você tivesse:

void Shniz(Foo foo, Bar bar, Baz baz, Quux quux, Fred fred, 
           Wilma wilma, Barney barney, Dino dino, Donkey donkey) { ...

Este é um caso muito diferente porque todos os objetos são diferentes (e não é provável que sejam confundidos). Concordou que se todos os objetos são necessários, e são todos diferentes, faz pouco sentido criar uma classe de parâmetro.

Além disso, alguns parâmetros são opcionais? Existem substituições de método (mesmo nome de método, mas assinaturas de método diferentes?) Todos esses tipos de detalhes importam quanto à melhor resposta.

* Uma bolsa de propriedades também pode ser útil, mas não especificamente melhor, visto que não há informações básicas fornecidas.

Como você pode ver, há mais de uma resposta correta para essa pergunta. Faça sua escolha.

Robert Paulson
fonte
3

Você pode tentar agrupar seu parâmetro em múltiplas estruturas / classes significativas (se possível).

Julien Hoarau
fonte
2

Eu geralmente me inclino para a abordagem de estruturas - presumivelmente a maioria desses parâmetros está relacionada de alguma forma e representa o estado de algum elemento que é relevante para o seu método.

Se o conjunto de parâmetros não puder ser transformado em um objeto significativo, provavelmente é um sinal de que Shnizestá fazendo muito, e a refatoração deve envolver a divisão do método em questões separadas.

Andrzej Doyle
fonte
2

Você pode trocar complexidade por linhas de código-fonte. Se o método em si fizer muito (canivete), tente reduzir as tarefas pela metade criando outro método. Se o método for simples, apenas precisa de muitos parâmetros, então os chamados objetos de parâmetro são o caminho a percorrer.

Karl
fonte
2

Se sua linguagem suportar, use parâmetros nomeados e torne tantos opcionais (com padrões razoáveis) quanto possível.

user54650
fonte
1

Acho que o método que você descreveu é o caminho a percorrer. Quando encontro um método com muitos parâmetros e / ou que provavelmente precisará de mais no futuro, geralmente crio um objeto ShnizParams para passar, como você descreve.

Grupo instantâneo
fonte
1

Que tal não configurá-lo de uma só vez nos construtores, mas sim por meio de propriedades / configuradores ? Eu vi algumas classes .NET que utilizam essa abordagem, como Processclasse:

        Process p = new Process();

        p.StartInfo.UseShellExecute = false;
        p.StartInfo.CreateNoWindow = true;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;
        p.StartInfo.FileName = "cmd";
        p.StartInfo.Arguments = "/c dir";
        p.Start();
Gant
fonte
3
C # 3 na verdade tem uma sintaxe para fazer isso facilmente: inicializadores de objeto.
Daren Thomas
1

Eu concordo com a abordagem de mover os parâmetros para um objeto de parâmetro (estrutura). Em vez de apenas colocá-los todos em um único objeto, analise se outras funções usam grupos de parâmetros semelhantes. Um objeto de parâmetro é mais valioso se for usado com várias funções em que você espera que esse conjunto de parâmetros mude consistentemente entre essas funções. Pode ser que você coloque apenas alguns dos parâmetros no novo objeto de parâmetro.

Frank Schwieterman
fonte
1

Se você tiver tantos parâmetros, é provável que o método esteja fazendo muito, então resolva isso primeiro dividindo o método em vários métodos menores. Se você ainda tiver muitos parâmetros depois disso, tente agrupar os argumentos ou transformar alguns dos parâmetros em membros de instância.

Prefira classes / métodos pequenos em vez de grandes. Lembre-se do princípio da responsabilidade única.

Brian Rasmussen
fonte
O problema com os membros e propriedades da instância é que eles 1) devem ser graváveis, 2) podem não ser configurados. No caso de um construtor, há certos campos que quero garantir que sejam preenchidos antes que uma instância possa existir.
recursivo
@recursive - discordo que os campos / propriedades sempre devem ser graváveis. Para turmas pequenas, muitas vezes os membros somente leitura fazem sentido.
Brian Rasmussen
1

Argumentos nomeados são uma boa opção (presumindo uma linguagem que os suporte) para eliminar a ambigüidade de listas de parâmetros longas (ou mesmo curtas!), Ao mesmo tempo que permite (no caso de construtores) que as propriedades da classe sejam imutáveis ​​sem impor um requisito para permitir sua existência em um estado parcialmente construído.

A outra opção que eu procuraria ao fazer esse tipo de refatoração seriam grupos de parâmetros relacionados que poderiam ser mais bem tratados como um objeto independente. Usando a classe Rectangle de uma resposta anterior como exemplo, o construtor que usa parâmetros para x, y, altura e largura poderia fatorar xey em um objeto Point, permitindo que você passasse três parâmetros para o construtor do Rectangle. Ou vá um pouco mais longe e faça dois parâmetros (UpperLeftPoint, LowerRightPoint), mas isso seria uma refatoração mais radical.

Dave Sherohman
fonte
0

Depende de que tipo de argumentos você tem, mas se eles forem muitos valores / opções booleanas, talvez você possa usar um Sinalizador Enum?

Scottm
fonte
0

Acho que esse problema está profundamente ligado ao domínio do problema que você está tentando resolver com a classe.

Em alguns casos, um construtor de 7 parâmetros pode indicar uma hierarquia de classes ruim: nesse caso, a estrutura / classe auxiliar sugerida acima é geralmente uma boa abordagem, mas você também tende a acabar com cargas de estruturas que são apenas pacotes de propriedade e não faça nada útil. O construtor de 8 argumentos também pode indicar que sua classe é muito genérica / muito multifacetada, portanto, precisa de muitas opções para ser realmente útil. Nesse caso, você pode refatorar a classe ou implementar construtores estáticos que ocultam os construtores complexos reais: por exemplo. Shniz.NewBaz (foo, bar) poderia realmente chamar o construtor real passando os parâmetros corretos.

axel_c
fonte
0

Uma consideração é qual dos valores seria somente leitura depois que o objeto fosse criado?

Propriedades graváveis ​​publicamente talvez possam ser atribuídas após a construção.

Em última análise, de onde vêm os valores? Talvez alguns valores sejam verdadeiramente externos, enquanto outros são realmente de alguma configuração ou dados globais mantidos pela biblioteca.

Nesse caso, você pode ocultar o construtor do uso externo e fornecer uma função Criar para ele. A função de criação pega os valores verdadeiramente externos e constrói o objeto, então usa acessores disponíveis apenas para a biblioteca para completar a criação do objeto.

Seria realmente estranho ter um objeto que requer 7 ou mais parâmetros para dar ao objeto um estado completo e todos sendo verdadeiramente de natureza externa.

AnthonyWJones
fonte
0

Quando um clas tem um construtor que aceita muitos argumentos, geralmente é um sinal de que ele tem muitas responsabilidades. Ele provavelmente pode ser dividido em classes separadas que cooperam para fornecer as mesmas funcionalidades.

No caso de você realmente precisar de tantos argumentos para um construtor, o padrão Builder pode ajudá-lo. O objetivo é ainda passar todos os argumentos para o construtor, de forma que seu estado seja inicializado desde o início e você ainda possa tornar a classe imutável, se necessário.

Ver abaixo :

public class Toto {
    private final String state0;
    private final String state1;
    private final String state2;
    private final String state3;

    public Toto(String arg0, String arg1, String arg2, String arg3) {
        this.state0 = arg0;
        this.state1 = arg1;
        this.state2 = arg2;
        this.state3 = arg3;
    }

    public static class TotoBuilder {
        private String arg0;
        private String arg1;
        private String arg2;
        private String arg3;

        public TotoBuilder addArg0(String arg) {
            this.arg0 = arg;
            return this;
        }
        public TotoBuilder addArg1(String arg) {
            this.arg1 = arg;
            return this;
        }
        public TotoBuilder addArg2(String arg) {
            this.arg2 = arg;
            return this;
        }
        public TotoBuilder addArg3(String arg) {
            this.arg3 = arg;
            return this;
        }

        public Toto newInstance() {
            // maybe add some validation ...
            return new Toto(this.arg0, this.arg1, this.arg2, this.arg3);
        }
    }

    public static void main(String[] args) {
        Toto toto = new TotoBuilder()
            .addArg0("0")
            .addArg1("1")
            .addArg2("2")
            .addArg3("3")
            .newInstance();
    }

}
Guillaume
fonte