Pergunta

Digamos que você tenha uma classe chamada Cliente, que contém os seguintes campos:

  • Nome de usuário
  • E-mail
  • Primeiro Nome
  • Último Nome

Vamos também dizer que, de acordo com a sua lógica de negócio, todos os objetos Cliente deve ter estas quatro propriedades definidas.

Agora, nós podemos fazer isso muito facilmente, forçando o construtor para especificar cada uma dessas propriedades.Mas é muito fácil ver como este pode sair do controle quando você é forçado a adicionar mais campos necessários para o objeto Cliente.

Eu vi classes que levam em 20+ argumentos em seu construtor e é apenas uma dor para usá-los.Mas, em alternativa, se você não exigem estes campos de você correr para o risco de ter indefinido informações, ou pior, o objecto de referência erros, se você confiar no código de chamada para especificar essas propriedades.

Existem alternativas para isso ou você você só tem que decidir se X quantidade de argumentos do construtor é demais para você conviver?

Foi útil?

Solução

Duas abordagens de design para considerar

O essência padrão

O interface fluente padrão

Ambos são semelhantes em intenção, em que nós lentamente construir um intermediário objeto e, em seguida, criar o nosso objeto de destino em uma única etapa.

Um exemplo da interface fluente em ação seria:

public class CustomerBuilder {
    String surname;
    String firstName;
    String ssn;
    public static CustomerBuilder customer() {
        return new CustomerBuilder();
    }
    public CustomerBuilder withSurname(String surname) {
        this.surname = surname; 
        return this; 
    }
    public CustomerBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this; 
    }
    public CustomerBuilder withSsn(String ssn) {
        this.ssn = ssn; 
        return this; 
    }
    // client doesn't get to instantiate Customer directly
    public Customer build() {
        return new Customer(this);            
    }
}

public class Customer {
    private final String firstName;
    private final String surname;
    private final String ssn;

    Customer(CustomerBuilder builder) {
        if (builder.firstName == null) throw new NullPointerException("firstName");
        if (builder.surname == null) throw new NullPointerException("surname");
        if (builder.ssn == null) throw new NullPointerException("ssn");
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.ssn = builder.ssn;
    }

    public String getFirstName() { return firstName;  }
    public String getSurname() { return surname; }
    public String getSsn() { return ssn; }    
}
import static com.acme.CustomerBuilder.customer;

public class Client {
    public void doSomething() {
        Customer customer = customer()
            .withSurname("Smith")
            .withFirstName("Fred")
            .withSsn("123XS1")
            .build();
    }
}

Outras dicas

Eu vejo que algumas pessoas estão recomendando sete anos, como um limite superior.Aparentemente, não é verdade que pessoas podem manter sete coisas na sua cabeça de uma vez;eles podem apenas lembre-se quatro (Susan Weinschenk, 100 Coisas que Todo Designer Precisa Saber sobre as Pessoas, 48).Mesmo assim, considero de quatro para ser algo de uma alta órbita da terra.Mas isso é porque o meu pensamento tem sido alterados por Bob Martin.

No Código Limpo, O tio Bob defende três como um limite superior para o número de parâmetros.Ele faz a radical afirmação (40):

O número ideal de argumentos para uma função é igual a zero (sem retorno).Em seguida, vem um (monádico), seguido de perto por dois (diádica).Três argumentos (triádica) deve ser evitada sempre que possível.Mais de três (polyadic) requer muito especial de justificação—e, em seguida, não deve ser usado de qualquer maneira.

Ele diz isso porque a leitura;mas também devido a capacidade de teste:

Imagine a dificuldade de escrever todos os casos de teste para garantir que todas as várias combinações de argumentos funcionar corretamente.

Eu encorajo você a encontrar uma cópia do seu livro e ler a sua discussão completa dos argumentos da função (40-43).

Eu concordo com aqueles que mencionaram o Único Princípio de Responsabilidade.É difícil para mim acreditar que uma classe que precisa de mais do que dois ou três valores/objetos sem predefinições razoáveis realmente tem apenas uma responsabilidade, e não seria melhor fora com outra classe extraídos.

Agora, se você está injetando suas dependências através do construtor, Bob Martin argumentos sobre o quão fácil é para invocar o construtor não tanto aplicar (porque, geralmente, em seguida, há apenas um ponto em seu aplicativo onde você arame que, ou você mesmo ter uma estrutura que faz isso para você).No entanto, o Único Princípio de Responsabilidade é ainda relevante:uma vez que uma classe tem quatro dependências, considero que um cheiro que ele está a fazer uma grande quantidade de trabalho.

No entanto, como com todas as coisas em ciência da computação, há, sem dúvida, válido casos por ter um grande número de construtor com parâmetros.Não se contorcer seu código para evitar o uso de um grande número de parâmetros;mas se você fizer uso de um grande número de parâmetros, parar e dar-lhe algum pensamento, porque pode significar o seu código já está contorcido.

No seu caso, fique com o construtor.A informação pertence ao Cliente e 4 campos são belas.

No caso de você ter muitos campos necessários e opcionais o construtor não é a melhor solução.Como o @boojiboy disse, é difícil de ler e também é difícil escrever o código do cliente.

@contagiosa sugeriu o uso do padrão e setters para opcional attributs.Que exige que os campos são mutáveis, mas isso é um problema menor.

Josué Bloco Eficaz Java 2 se dizer que, neste caso, você deve considerar um construtor.Um exemplo retirado do livro:

 public class NutritionFacts {  
   private final int servingSize;  
   private final int servings;  
   private final int calories;  
   private final int fat;  
   private final int sodium;  
   private final int carbohydrate;  

   public static class Builder {  
     // required parameters  
     private final int servingSize;  
     private final int servings;  

     // optional parameters  
     private int calories         = 0;  
     private int fat              = 0;  
     private int carbohydrate     = 0;  
     private int sodium           = 0;  

     public Builder(int servingSize, int servings) {  
      this.servingSize = servingSize;  
       this.servings = servings;  
    }  

     public Builder calories(int val)  
       { calories = val;       return this; }  
     public Builder fat(int val)  
       { fat = val;            return this; }  
     public Builder carbohydrate(int val)  
       { carbohydrate = val;   return this; }  
     public Builder sodium(int val)  
       { sodium = val;         return this; }  

     public NutritionFacts build() {  
       return new NutritionFacts(this);  
     }  
   }  

   private NutritionFacts(Builder builder) {  
     servingSize       = builder.servingSize;  
     servings          = builder.servings;  
     calories          = builder.calories;  
     fat               = builder.fat;  
     soduim            = builder.sodium;  
     carbohydrate      = builder.carbohydrate;  
   }  
}  

E, em seguida, usá-lo assim:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
      calories(100).sodium(35).carbohydrate(27).build();

O exemplo acima foi retirado de Eficaz Java 2

E isso não se aplica somente para o construtor.Citando Kent Beck em Padrões De Implementação:

setOuterBounds(x, y, width, height);
setInnerBounds(x + 2, y + 2, width - 4, height - 4);

Fazer o retângulo explícito como um objeto explica o código melhor:

setOuterBounds(bounds);
setInnerBounds(bounds.expand(-2));

Eu acho que o "puro OOP", a resposta é que, se as operações na classe são inválidas quando certos membros não são inicializados, em seguida, esses membros devem ser definidos pelo construtor.Sempre há o caso onde os valores padrão podem ser usadas, mas eu vou assumir que não estamos considerando o caso.Esta é uma boa aproximação quando a API é fixa, uma vez que alterar o único permitido construtor após a API vem a público vai ser um pesadelo para você e todos os usuários do seu código.

Em C#, o que eu entendo sobre as diretrizes de design é que este não é necessariamente a única maneira de lidar com a situação.Particularmente com o WPF objetos, você vai encontrar isso .NET classes tendem a favorecer construtores sem parâmetros e irá lançar exceções se os dados não foi inicializado para um estado desejável antes de chamar o método.Este é, provavelmente, principalmente específicos para o componente de um projeto baseado em que;Eu não posso vir acima com um exemplo concreto de um .NET classe que se comporta dessa maneira.No seu caso, ele seria certamente causar um aumento do peso em testes para garantir que a classe nunca é salvo para o armazenamento de dados, a menos que as propriedades tenham sido validados.Honestamente, por isso eu prefiro o "construtor define as propriedades necessárias" abordagem se sua API é um conjunto em pedra ou não pública.

A única coisa que eu am certo é que, provavelmente, existem inúmeras metodologias que podem resolver esse problema, e cada um deles apresenta seu próprio conjunto de problemas.A melhor coisa a fazer é aprender como muitos padrões possíveis e escolher o melhor para o trabalho.(Não é como um cop-out de uma resposta?)

Eu acho que a sua pergunta é mais sobre o design de suas aulas do que sobre o número de argumentos do construtor.Se eu precisasse de 20 pedaços de dados (argumentos) para inicializar com êxito um objeto, eu seria, provavelmente, considere a possibilidade de dividir a classe.

Steve Mcconnell escreve em Código de Concluir que as pessoas têm dificuldade em manter mais 7 coisas na sua cabeça a um tempo, então isso seria o número que eu tente ficar embaixo.

Se você tem unpalatably muitos argumentos, então é só pacote-los juntos em estruturas / POD classes, de preferência declarada como classes internas da classe que você está construindo.De que maneira você pode ainda exigir que os campos ao fazer o código que chama o construtor razoavelmente legível.

Eu acho que tudo depende da situação.Para algo como o seu exemplo, uma classe de cliente, eu não iria risco a possibilidade de ter que dados a ser indefinido, quando necessário.Por outro lado, a passagem de uma estrutura que iria limpar a lista de argumentos, mas você ainda tem um monte de coisas para definir a estrutura.

Eu acho que a maneira mais fácil seria encontrar um padrão aceitável para cada valor.Neste caso, cada campo parece que seria necessário para construir, assim, possivelmente, a função de sobrecarga de chamada, de modo que, se algo não está definido na convocação, para definir um padrão.

Em seguida, fazer funções de getter e setter para cada propriedade, de modo que os valores padrão pode ser alterado.

Implementação em Java:

public static void setEmail(String newEmail){
    this.email = newEmail;
}

public static String getEmail(){
    return this.email;
}

Esta é também uma boa prática para manter o seu variáveis globais seguro.

O estilo é muito importante, e parece-me que, se há um construtor com 20 ou mais argumentos, em seguida, o projeto deve ser alterado.Fornecem predefinições razoáveis.

Concordo, a 7 de limite de item Boojiboy menciona.Além do que, ele pode ser vale a pena olhar anônimo (ou especializado) tipos de IDictionary, ou indireto através de chave primária de outra fonte de dados.

Eu encapsular os campos similares em um objeto do seu próprio, com a sua própria construção/validação lógica.

Digamos, por exemplo, se você tem

  • BusinessPhone
  • BusinessAddress
  • HomePhone
  • HomeAddress

Eu gostaria de fazer uma classe que armazena telefone e endereço junto com uma etiqueta especificando se sua "casa" ou um "negócio", telefone/endereço.E, em seguida, reduza a 4 campos para apenas uma matriz.

ContactInfo cinfos = new ContactInfo[] {
    new ContactInfo("home", "+123456789", "123 ABC Avenue"),
    new ContactInfo("biz", "+987654321", "789 ZYX Avenue")
};

Customer c = new Customer("john", "doe", cinfos);

Que deve fazê-lo parecer menos como espaguete.

Certamente, se você tem um monte de campos, deve haver algum padrão que você pode extrair o que gostaria de fazer uma agradável unidade de função de seu próprio.E tornar mais legível o código também.

E o seguinte é também possíveis soluções:

  • Espalhe a lógica de validação em vez de armazená-lo em uma única classe.Valida quando o usuário digitá-las e, em seguida, validar novamente na camada de banco de dados etc...
  • Fazer um CustomerFactory classe que poderia me ajudar a construir Customers
  • @marcio da solução também é interessante...

Basta usar argumentos padrão.Em uma linguagem que suporta o padrão argumentos do método (PHP, por exemplo), você pode fazer isso na assinatura do método:

public function doSomethingWith($this = val1, $this = val2, $this = val3)

Existem outras maneiras de criar valores padrão, tais como em linguagens com suporte para sobrecarga de método.

É claro, você também pode definir valores padrão quando você declarar os campos, se o considerar adequado para fazê-lo.

Ele realmente vem para baixo se ele é ou não apropriado para definir esses valores padrão, ou se os objetos devem ser specced fora em construção o tempo todo.Essa é realmente uma decisão que só você pode fazer.

A menos que seja de mais de 1 argumento, eu sempre uso de arrays ou objetos como construtor de parâmetros e dependem de erro de verificação para se certificar de que os parâmetros necessários estão lá.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top