Optionale Parameter oder überladene Konstruktoren

28

Ich implementiere eine DelegateCommand, und als ich den oder die Konstruktoren implementieren wollte, hatte ich die folgenden zwei Entwurfsoptionen:

1: Mehrere überladene Konstruktoren

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

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

2: Nur einen Konstruktor mit einem optionalen Parameter

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

Ich weiß nicht, welchen ich verwenden soll, weil ich nicht weiß, welche möglichen Vor- / Nachteile mit einem der beiden vorgeschlagenen Wege verbunden sind. Beide können wie folgt aufgerufen werden:

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

Kann mich bitte jemand in die richtige Richtung weisen und Feedback geben?

Thomas Flinkow
quelle
4
KISS und YAGNI. Im Zweifelsfall implementieren Sie beide. Suchen Sie nach einer Weile nach Verweisen auf beide Konstruktoren. Es würde mich nicht überraschen, wenn einer von ihnen niemals extern konsumiert würde. Oder am Rande konsumiert. Dies ist leicht zu finden, wenn eine statische Analyse des Codes durchgeführt wird.
Laiv
1
Statische Konstruktoren (wie Bitmap.FromFile) sind ebenfalls eine Option
BlueRaja - Danny Pflughoeft
3
Code-Analyse-Regel einhalten CA1026: Standardparameter sollten nicht berücksichtigt werden.
Dan Lyons
2
@Laiv vermisse ich etwas? Sie werden auf die gleiche Weise verwendet und haben daher die gleiche Signatur. Sie können nicht nach Referenzen suchen und feststellen, an welche der Anrufer gedacht hat. Es hört sich eher so an, als würden Sie sich darauf beziehen, ob OP überhaupt dieses zweite Argument haben sollte oder nicht, aber das ist nicht das, worum es bei OP geht (und sie wissen sicher, dass sie manchmal beides brauchen, daher fragen sie).
Kat
2
@Voo Der Compiler für Openedge | Progress 10.2B ignoriert sie. Wir haben unsere Strategie zur Änderung der Methoden so gut wie umgebracht. Stattdessen mussten wir Überladung für den gleichen Effekt verwenden.
Bret

Antworten:

24

Ich bevorzuge mehrere Konstruktoren gegenüber Standardwerten und persönlich mag ich Ihr Beispiel mit zwei Konstruktoren nicht, es sollte anders implementiert werden.

Der Grund für die Verwendung mehrerer Konstruktoren ist, dass der Hauptkonstruktor nur prüfen kann, ob alle Parameter nicht null und gültig sind, während andere Konstruktoren Standardwerte für den Hauptkonstruktor bereitstellen können.

In Ihren Beispielen besteht jedoch kein Unterschied zwischen ihnen, da selbst der sekundäre Konstruktor einen nullals Standardwert übergibt und der primäre Konstruktor auch den Standardwert kennen muss. Ich denke, das sollte nicht so sein.

Dies bedeutet, dass es sauberer und besser getrennt wäre, wenn es auf diese Weise implementiert würde:

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(..);
}

Beachten Sie den _ => truean den primären Konstruktor übergebenen Konstruktor, der jetzt auch alle Parameter überprüft nullund sich nicht um Standardeinstellungen kümmert.


Der wichtigste Punkt ist jedoch die Erweiterbarkeit. Mehrere Konstruktoren sind sicherer, wenn die Möglichkeit besteht, dass Sie Ihren Code in Zukunft erweitern. Wenn Sie weitere erforderliche Parameter hinzufügen und die optionalen am Ende stehen müssen, brechen Sie alle aktuellen Implementierungen ab. Sie können den alten Konstruktor [Obsolete]erstellen und die Benutzer darüber informieren, dass er entfernt wird, damit sie Zeit für die Migration auf die neue Implementierung haben, ohne ihren Code sofort zu beschädigen.


Auf der anderen Seite wäre es auch verwirrend, zu viele Parameter optional zu machen, denn wenn einige in einem Szenario erforderlich und in einem anderen optional sind, müssten Sie die Dokumentation studieren, anstatt nur den richtigen Konstruktor durch einfaches Betrachten der Parameter auszuwählen.

t3chb0t
quelle
12
Andererseits wäre es verwirrend, zu viele Parameter optional zu machen ... Um ehrlich zu sein, ist es verwirrend , zu viele Parameter zu haben (unabhängig davon, ob sie optional sind oder nicht).
Andy
1
@ DavidPacker Ich stimme Ihnen zu, aber wie viele Parameter zu viele sind, ist eine andere Geschichte, denke ich
;-)
2
Es gibt einen weiteren Unterschied zwischen einem Konstruktor, der einen Parameter auslässt, und einem Konstruktor, der einen Standard für den Parameter hat. Im ersten Fall kann der Aufrufer die Übergabe des Parameters explizit vermeiden, im zweiten Fall kann der Aufrufer dies nicht - da die Übergabe eines Parameters mit der Übergabe des Standardwerts identisch ist. Wenn der Konstruktor diese Unterscheidung treffen muss, sind nur zwei Konstruktoren möglich.
Gary McGill
1
Angenommen, ich habe eine Klasse, die Zeichenfolgen formatiert, die ich optional mit einem CultureInfoObjekt erstellen kann. Ich würde eine API bevorzugen , die die zulässigen CultureInfoParameter über einen zusätzlichen Parameter geliefert werden, bestehen aber darauf , dass , wenn es wurde geliefert , dann sollte es nicht sein null. Auf diese Weise werden zufällige nullWerte nicht als "keine" interpretiert.
Gary McGill
Ich bin nicht sicher, ob die Erweiterbarkeit wirklich ein Grund gegen optionale Parameter ist. Wenn Sie überhaupt Parameter benötigt haben und deren Anzahl ändern, müssen Sie einen neuen und Obsoleteden alten Konstruktor erstellen, wenn Sie verhindern möchten, dass Fehler auftreten, unabhängig davon, ob Sie auch optionale Parameter haben oder nicht. Mit optionalen Parametern müssen Sie nur, Obsoletewenn Sie einen entfernen.
Herohtar
17

In Anbetracht der Tatsache, dass Sie im Konstruktor nur einfache Aufgaben ausführen, ist die Einzelkonstruktorlösung in Ihrem Fall möglicherweise die bessere Wahl. Der andere Konstruktor bietet keine zusätzlichen Funktionen. Aus dem Design des Konstruktors mit zwei Parametern geht hervor, dass das zweite Argument nicht angegeben werden muss.

Mehrere Konstruktoren sind sinnvoll, wenn Sie ein Objekt aus verschiedenen Typen erstellen. In diesem Fall sind die Konstruktoren ein Ersatz für eine externe Factory, da sie die Eingabeparameter verarbeiten und sie in eine korrekte Darstellung der internen Eigenschaften der Klasse formatieren, die erstellt wird. Dies ist in Ihrem Fall jedoch nicht der Fall, weshalb ein einziger Konstruktor mehr als genug sein sollte.

Andy
quelle
3

Ich denke, es gibt zwei verschiedene Fälle, bei denen jeder Ansatz glänzen kann.

Erstens, wenn wir einfache Konstruktoren haben (was meiner Erfahrung nach normalerweise der Fall ist), würde ich optionale Argumente in Betracht ziehen, um zu glänzen.

  1. Sie minimieren den Code, der geschrieben (und damit auch gelesen) werden muss.
  2. Sie können sicherstellen, dass sich die Dokumentation an einem Ort befindet und nicht wiederholt wird (was schlecht ist, weil dadurch ein zusätzlicher Bereich geöffnet wird, der veraltet werden könnte).
  3. Wenn Sie viele optionale Argumente haben, können Sie eine sehr verwirrende Zusammenstellung von Konstruktorkombinationen vermeiden. Selbst mit nur 2 optionalen Argumenten (unabhängig voneinander) müssten Sie 4 Konstruktoren haben, wenn Sie separate, überladene Konstruktoren benötigen (Version ohne, Version mit jedem und Version mit beiden). Dies ist offensichtlich nicht gut skalierbar.

Buuuut, es gibt Fälle, in denen optionale Argumente in Konstruktoren die Dinge nur deutlich verwirrender machen. Das offensichtliche Beispiel ist, wenn diese optionalen Argumente exklusiv sind (dh nicht zusammen verwendet werden können). Überladene Konstruktoren, die nur die tatsächlich gültigen Kombinationen von Argumenten zulassen, würden die Durchsetzung dieser Einschränkung zur Kompilierzeit sicherstellen. Sie sollten jedoch auch vermeiden, auf diesen Fall zu stoßen (z. B. wenn mehrere Klassen von einer Basis erben, wobei jede Klasse das exklusive Verhalten ausführt).

Kat
quelle
+1 für die Erwähnung der Permutationsexplosion mit mehreren optionalen Parametern.
Jpsy