Parâmetros opcionais ou construtores sobrecarregados


28

Estou implementando a DelegateCommande, quando estava prestes a implementar o (s) construtor (es), criei as duas seguintes opções de design:

1: Tendo vários construtores sobrecarregados

public DelegateCommand(Action<T> execute) : this(execute, null) { }

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
    this.execute = execute;
    this.canExecute = canExecute;
}

2: Ter apenas um construtor com um parâmetro opcional

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
    this.execute = execute;
    this.canExecute = canExecute;
}

Não sei qual usar, porque não sei quais vantagens / desvantagens possíveis vêm com uma das duas formas propostas. Ambos podem ser chamados assim:

var command = new DelegateCommand(this.myExecute);
var command2 = new DelegateCommand(this.myExecute, this.myCanExecute);

Alguém pode me indicar a direção certa e dar feedback?


4
BEIJO e YAGNI. Em caso de dúvida, implemente os dois. Depois de um tempo, faça uma pesquisa de referências aos dois construtores. Não me surpreenderia se um deles nunca fosse consumido externamente. Ou consumido marginalmente. É algo fácil de encontrar executando análises estáticas do código.
Mariv

11
Construtores estáticos (como Bitmap.FromFile) também são uma opção
BlueRaja - Danny Pflughoeft 27/03

3
Mantenha a regra de análise de código CA1026: Os parâmetros padrão não devem ser usados na mente.
Dan Lyons

2
@ Laiv Estou faltando alguma coisa? Eles são usados ​​da mesma maneira e, portanto, têm a mesma assinatura. Você não pode procurar referências e dizer em que o chamador estava pensando. Parece que você está se referindo a se o OP deve ou não ter esse segundo argumento, mas não é sobre isso que o OP está perguntando (e eles podem muito bem saber com certeza que às vezes precisam dos dois, daí a pergunta).
Kat

2
@Voo O compilador do Openedge | Progress 10.2B os ignora. Praticamente matou nossa estratégia de mudança de método ininterrupta; em vez disso, precisávamos usar sobrecarga para o mesmo efeito.
Bret

Respostas:


24

Eu prefiro vários construtores do que valores padrão e, pessoalmente, não gosto do seu exemplo de dois construtores, ele deve ser implementado de maneira diferente.

A razão para usar vários construtores é que o principal pode apenas verificar se todos os parâmetros não são nulos e se são válidos, enquanto outros construtores podem fornecer valores padrão para o principal.

Nos seus exemplos, no entanto, não há diferença entre eles, porque mesmo o construtor secundário passa a nullcomo valor padrão e o construtor primário também deve conhecer o valor padrão. Eu acho que não deveria.

Isso significa que seria mais limpo e melhor separado se implementado desta maneira:

public DelegateCommand(Action<T> execute) : this(execute, _ => true) { }

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
    this.execute = execute ?? throw new ArgumentNullException(..);
    this.canExecute = canExecute ?? throw new ArgumentNullException(..);
}

observe o _ => truepassado ao construtor principal que agora também está verificando todos os parâmetros nulle não se importa com nenhum padrão.


O ponto mais importante, porém, é a extensibilidade. Vários construtores são mais seguros quando existe a possibilidade de você estender seu código no futuro. Se você adicionar mais parâmetros obrigatórios, e os opcionais devem vir no final, você quebrará todas as suas implementações atuais. Você pode criar o construtor antigo [Obsolete]e informar aos usuários que ele será removido, dando-lhes tempo para migrar para a nova implementação sem quebrar instantaneamente o código.


Por outro lado, tornar muitos parâmetros opcionais também seria confuso, porque se alguns deles forem necessários em um cenário e opcionais em outro, você precisará estudar a documentação em vez de apenas escolher o construtor certo, simplesmente analisando seus parâmetros.


12
Por outro lado, tornar muitos parâmetros opcionais seria confuso ... Para ser sincero, ter muitos parâmetros (independentemente de serem opcionais ou não) é confuso.
Andy

11
@DavidPacker eu concordo com você, mas quantos parâmetros são muitos é outra história, eu acho ;-)
t3chb0t

2
Há outra diferença entre ter um construtor que omite um parâmetro e um construtor que possui um padrão para o parâmetro. No primeiro caso, o chamador pode evitar explicitamente passar o parâmetro, enquanto no último caso o chamador não pode - porque passar nenhum parâmetro é o mesmo que passar o valor padrão. Se o construtor precisar fazer essa distinção, dois construtores são o único caminho a percorrer.
Gary McGill

11
Por exemplo, suponha que eu tenho uma classe que formata seqüências de caracteres, que opcionalmente posso construir com um CultureInfoobjeto. Eu preferiria uma API que permitiu que o CultureInfoparâmetro a ser fornecida através de um parâmetro adicional, mas insistiu que, se foi fornecido, então ele deve não ser null. Dessa forma, nullvalores acidentais não são mal interpretados como significando "nenhum".
Gary McGill

Não tenho certeza se a extensibilidade é realmente uma razão contra parâmetros opcionais; se você tiver exigido parâmetros e alterar o número deles, precisará criar um novo construtor e Obsoleteo antigo, se desejar evitar quebras, se você também possui parâmetros opcionais ou não. Com parâmetros opcionais, você só precisará Obsoleteremover um.
Herohtar 27/03

17

Considerando que a única coisa que você está fazendo no construtor é uma atribuição simples, a solução de construtor único pode ser uma escolha melhor no seu caso. O outro construtor não fornece funcionalidade extra e fica claro no design do construtor com dois parâmetros que o segundo argumento não precisa ser fornecido.

Vários construtores fazem sentido quando você está construindo um objeto de diferentes tipos. Nesse caso, os construtores são uma substituição de uma fábrica externa porque processam os parâmetros de entrada e os formatam para uma representação correta da propriedade interna da classe que está sendo construída. No entanto, isso não acontece no seu caso, portanto, por que um único construtor deve ser mais do que suficiente.


3

Eu acho que há dois casos diferentes para os quais cada abordagem pode brilhar.

Em primeiro lugar, quando temos construtores simples (o que geralmente é o caso, na minha experiência), consideraria argumentos opcionais brilhantes.

  1. Eles minimizam o código que deve ser escrito (e, portanto, também o que deve ser lido).
  2. Você pode garantir que a documentação esteja em um só lugar e não seja repetida (o que é ruim porque abre uma área extra que pode ficar desatualizada).
  3. Se você tiver muitos argumentos opcionais, poderá evitar um conjunto muito confuso de combinações de construtores. Caramba, mesmo com apenas 2 argumentos opcionais (não relacionados entre si), se você quisesse construtores separados e sobrecarregados, teria que ter 4 construtores (versão sem nenhum, versão com cada um e versão com ambos). Obviamente, isso não escala bem.

No entanto, há casos em que argumentos opcionais em construtores apenas tornam as coisas claramente mais confusas. O exemplo óbvio é quando esses argumentos opcionais são exclusivos (ou seja, não podem ser usados ​​juntos). Ter construtores sobrecarregados que permitam apenas as combinações realmente válidas de argumentos garantiria a imposição em tempo de compilação dessa restrição. Dito isso, você também deve evitar encontrar esse caso (por exemplo, com várias classes herdadas de uma base, onde cada classe faz o comportamento exclusivo).


+1 por mencionar a explosão de permutação com vários parâmetros opcionais.
Jpsy
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.