Parámetros opcionales o constructores sobrecargados

28

Estoy implementando un DelegateCommand, y cuando estaba a punto de implementar los constructores, se me ocurrieron las siguientes dos opciones de diseño:

1: Tener múltiples constructores sobrecargados

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

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

2: Tener solo un constructor con un parámetro opcional

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

No sé cuál usar porque no sé qué posibles ventajas / desventajas vienen con cualquiera de las dos formas propuestas. Ambos se pueden llamar así:

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

¿Puede alguien señalarme en la dirección correcta y dar su opinión?

Thomas Flinkow
fuente
44
BESO Y YAGNI. En caso de dudas, implemente ambos. Después de un tiempo, haga una búsqueda de referencias a ambos constructores. No me sorprendería si uno de ellos nunca se consumiera externamente. O consumido marginalmente. Esto es algo fácil de encontrar realizando un análisis estático del código.
Laiv
1
Los constructores estáticos (como Bitmap.FromFile) también son una opción
BlueRaja - Danny Pflughoeft
3
Mantenga la regla de análisis de código CA1026: no se deben tener en cuenta los parámetros predeterminados .
Dan Lyons
2
@Laiv ¿Me estoy perdiendo algo? Se usan de la misma manera y, por lo tanto, tienen la misma firma. No puede buscar referencias y decir en qué estaba pensando la persona que llama. Parece más como si te estuvieras refiriendo a si OP debería tener o no ese segundo argumento, pero eso no es lo que OP está preguntando (y es muy posible que sepan con certeza que a veces necesitan ambos, por lo tanto preguntan).
Kat
2
@Voo El compilador para Openedge | Progress 10.2B los ignora. Prácticamente mató nuestra estrategia de cambio de método sin interrupciones; en cambio, necesitábamos usar sobrecarga para el mismo efecto.
Bret

Respuestas:

24

Prefiero múltiples constructores sobre valores predeterminados y personalmente no me gusta su ejemplo de dos constructores, debería implementarse de manera diferente.

La razón para usar múltiples constructores es que el principal solo puede verificar si todos los parámetros no son nulos y si son válidos, mientras que otros constructores pueden proporcionar valores predeterminados para el principal.

Sin embargo, en sus ejemplos, no hay diferencia entre ellos porque incluso el constructor secundario pasa a nullcomo valor predeterminado y el constructor primario también debe conocer el valor predeterminado. Creo que no debería.

Esto significa que estaría más limpio y mejor separado si se implementa de esta manera:

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 la _ => true pasado al constructor primario que ahora también está verificando todos los parámetros nully no le importan los valores predeterminados.


Sin embargo, el punto más importante es la extensibilidad. Los constructores múltiples son más seguros cuando existe la posibilidad de que extienda su código en el futuro. Si agrega más parámetros requeridos, y los opcionales deben aparecer al final, interrumpirá todas sus implementaciones actuales. Puedes hacer el viejo constructor[Obsolete] e informar a los usuarios que se eliminará, dándoles tiempo para migrar a la nueva implementación sin romper su código instantáneamente.


Por otro lado, hacer que muchos parámetros sean opcionales también sería confuso porque si algunos de ellos son necesarios en un escenario y opcionales en otro, necesitaría estudiar la documentación en lugar de elegir el constructor correcto simplemente mirando sus parámetros.

t3chb0t
fuente
12
Por otro lado, hacer demasiados parámetros opcionales sería confuso ... Para ser sincero, tener demasiados parámetros (independientemente de si son opcionales o no) es confuso.
Andy
1
@DavidPacker Estoy de acuerdo con usted, pero cuántos parámetros son demasiados es otra historia, creo ;-)
t3chb0t
2
Hay otra diferencia entre tener un constructor que omite un parámetro frente a un constructor que tiene un valor predeterminado para el parámetro. En el primer caso, la persona que llama puede evitar explícitamente pasar el parámetro, mientras que en el último caso no puede hacerlo, porque pasar ningún parámetro es lo mismo que pasar el valor predeterminado. Si el constructor necesita hacer esta distinción, entonces dos constructores es el único camino a seguir.
Gary McGill
1
Por ejemplo, supongamos que tengo una clase que formatea cadenas, que opcionalmente puedo construir con un CultureInfoobjeto. Yo preferiría una API que permite que el CultureInfoparámetro que se suministra a través de un parámetro adicional, pero insistió en que si se suministra entonces debería no ser null. De esta forma, los nullvalores accidentales no se interpretan mal como "ninguno".
Gary McGill
No estoy seguro de que la extensibilidad sea realmente una razón contra los parámetros opcionales; Si tiene parámetros necesarios y alguna vez cambia el número de ellos, tendrá que crear un nuevo constructor y Obsoleteel antiguo si desea evitar romper cosas, ya sea que tenga parámetros opcionales o no. Con parámetros opcionales solo tiene que hacerlo Obsoletesi elimina uno.
Herohtar
17

Teniendo en cuenta que lo único que está haciendo en el constructor es una asignación simple, la solución de constructor único puede ser una mejor opción en su caso. El otro constructor no proporciona ninguna funcionalidad adicional y, a partir del diseño del constructor con dos parámetros, queda claro que no es necesario proporcionar el segundo argumento.

Múltiples constructores tienen sentido cuando estás construyendo un objeto de diferentes tipos. En ese caso, los constructores son una sustitución de una fábrica externa porque procesan los parámetros de entrada y los dan formato a una representación de propiedad interna correcta de la clase que se está construyendo. Sin embargo, eso no sucede en su caso, por lo tanto, un solo constructor debería ser más que suficiente.

Andy
fuente
3

Creo que hay dos casos diferentes para los cuales cada enfoque puede brillar.

En primer lugar, cuando tenemos constructores simples (que suele ser el caso, en mi experiencia), consideraría argumentos opcionales para brillar.

  1. Minimizan el código que debe escribirse (y, por lo tanto, también debe leerse).
  2. Puede asegurarse de que la documentación esté en un lugar y no se repita (lo cual es malo porque abre un área adicional que podría quedar desactualizada).
  3. Si tiene muchos argumentos opcionales, puede evitar tener un conjunto muy confuso de combinaciones de constructores. Diablos, incluso con solo 2 argumentos opcionales (no relacionados entre sí), si quisieras constructores sobrecargados por separado, tendrías que tener 4 constructores (versión sin ninguno, versión con cada uno y versión con ambos). Obviamente, esto no escala bien.

Pero, hay casos en los que los argumentos opcionales en los constructores simplemente hacen que las cosas sean claramente más confusas. El ejemplo obvio es cuando estos argumentos opcionales son exclusivos (es decir, no se pueden usar juntos). Tener constructores sobrecargados que solo permitan las combinaciones de argumentos realmente válidas garantizaría la aplicación en tiempo de compilación de esta restricción. Dicho esto, también debe evitar incluso encontrar este caso (por ejemplo, con múltiples clases heredadas de una base, donde cada clase realiza el comportamiento exclusivo).

Kat
fuente
+1 por mencionar la explosión de permutación con múltiples parámetros opcionales.
Jpsy