Необязательные параметры или перегруженные конструкторы

28

Я реализую DelegateCommand, и когда я собирался реализовать конструктор (ы), я предложил следующие два варианта дизайна:

1: Наличие нескольких перегруженных конструкторов

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

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

2: Наличие только одного конструктора с необязательным параметром

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

Я не знаю, какой из них использовать, потому что я не знаю, какие возможные преимущества / недостатки связаны с любым из двух предложенных способов. И то, и другое можно назвать так:

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

Может кто-нибудь указать мне правильное направление и дать отзыв?

Томас Флинков
источник
4
ПОЦЕЛУЙ и ЯГНИ. В случае сомнений, реализовать оба. Через некоторое время выполните поиск ссылок на оба конструктора. Меня не удивило бы, если бы один из них никогда не употреблялся извне. Или незначительно потребляется. Это легко найти, выполняя статический анализ кода.
Laiv
1
Статические конструкторы (как Bitmap.FromFile) также вариант
BlueRaja - Дэнни Pflughoeft
3
Сохранить правило анализа кода CA1026: параметры по умолчанию не следует использовать .
Дэн Лайонс
2
@Laiv я что-то упустил? Они используются одинаково и поэтому имеют одинаковую подпись. Вы не можете искать ссылки и сказать, о чем думал вызывающий абонент. Больше похоже на то, что вы имеете в виду, должен ли OP иметь этот второй аргумент или нет, но это не то, о чем спрашивает OP (и они могут точно знать, что иногда им нужны оба, следовательно, они спрашивают).
Kat
2
@Voo Компилятор для Openedge | Progress 10.2B игнорирует их. В значительной степени убил нашу непоколебимую стратегию смены методов; вместо этого нам нужно было использовать перегрузку для того же эффекта.
Брет

Ответы:

24

Я предпочитаю несколько конструкторов значениям по умолчанию, и лично мне не нравятся ваши два примера конструктора, они должны быть реализованы по-разному.

Причина использования нескольких конструкторов заключается в том, что основной может просто проверить, все ли параметры не равны NULL и являются ли они действительными, тогда как другие конструкторы могут предоставить значения по умолчанию для основного.

В ваших примерах, однако, нет никакой разницы между ними, потому что даже вторичный конструктор передает значение nullпо умолчанию, и первичный конструктор также должен знать значение по умолчанию. Я думаю, что это не должно.

Это означает, что он будет чище и лучше отделен, если будет реализован следующим образом:

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

обратите внимание на _ => trueпереданный первичному конструктору, который теперь также проверяет все параметры nullи не заботится о любых значениях по умолчанию.


Однако наиболее важным моментом является расширяемость. Несколько конструкторов безопаснее, когда есть вероятность, что вы будете расширять свой код в будущем. Если вы добавите больше обязательных параметров, а необязательные должны появиться в конце, вы сломаете все свои текущие реализации. Вы можете создать старый конструктор [Obsolete]и проинформировать пользователей о том, что он будет удален, что даст им время для перехода на новую реализацию без мгновенного нарушения кода.


С другой стороны, слишком большое количество необязательных параметров может привести к путанице, потому что если некоторые из них требуются в одном сценарии и необязательны в другом, вам нужно будет изучить документацию, а не просто выбирать правильный конструктор, просто просматривая его параметры.

t3chb0t
источник
12
С другой стороны, слишком большое количество необязательных параметров может привести к путанице ... Честно говоря, слишком большое количество параметров (независимо от того, являются ли они необязательными или нет) сбивает с толку.
Энди
1
@DavidPacker Я согласен с вами, но слишком много параметров
отдельная
2
Существует еще одно различие между наличием конструктора, в котором отсутствует параметр, и конструктором, который имеет значение по умолчанию для этого параметра. В первом случае вызывающая сторона может явно избежать передачи параметра, тогда как в последнем случае вызывающая сторона не может - потому что передача параметра не совпадает с передачей значения по умолчанию. Если конструктор должен сделать это различие, тогда два конструктора - единственный путь.
Гэри МакГилл,
1
Например, предположим, у меня есть класс, который форматирует строки, которые я могу при желании создать с помощью CultureInfoобъекта. Я бы предпочел API, который позволял предоставлять CultureInfoпараметр через дополнительный параметр, но настаивал на том, что если он был предоставлен, то этого не должно быть null. Таким образом, случайные nullзначения не интерпретируются как означающие «нет».
Гэри Макгилл,
Я не уверен , что растяжимость действительно причина против необязательных параметров; если у вас вообще есть необходимые параметры и когда-либо изменяете их количество, вам придется создать новый конструктор и Obsoleteстарый, если вы хотите избежать поломок, независимо от того, есть ли у вас дополнительные параметры или нет. С необязательными параметрами вам нужно только, Obsoleteесли вы удалите один.
Herohtar
17

Принимая во внимание единственное, что вы делаете в конструкторе - это простое присваивание, решение с одним конструктором может быть лучшим выбором в вашем случае. Другой конструктор не предоставляет никакой дополнительной функциональности, и из конструктора конструктора с двумя параметрами ясно, что второй аргумент указывать не нужно.

Несколько конструкторов имеют смысл, когда вы создаете объект из разных типов. В этом случае конструкторы являются заменой внешней фабрики, поскольку они обрабатывают входные параметры и форматируют их в правильное представление внутреннего свойства класса, который создается. Однако в вашем случае этого не происходит, поэтому одного конструктора должно быть более чем достаточно.

Энди
источник
3

Я думаю, что есть два разных случая, для которых каждый подход может проявить себя.

Во-первых, когда у нас есть простые конструкторы (что, по моему опыту, обычно так), я бы рассматривал необязательные аргументы, чтобы сиять.

  1. Они минимизируют код, который должен быть написан (и, следовательно, также должен быть прочитан).
  2. Вы можете убедиться, что документация находится в одном месте и не повторяется (что плохо, поскольку открывает дополнительную область, которая может устареть).
  3. Если у вас много необязательных аргументов, вы можете избежать очень запутанного набора комбинаций конструкторов. Черт возьми, даже с двумя необязательными аргументами (не связанными друг с другом), если вам нужны отдельные перегруженные конструкторы, вам нужно иметь 4 конструктора (версия без таковой, версия с каждым и версия с обоими). Это явно плохо масштабируется.

Buuuut, есть случаи, когда необязательные аргументы в конструкторах просто делают вещи более запутанными. Очевидный пример - когда эти необязательные аргументы являются исключительными (т. Е. Не могут использоваться вместе). Перегрузка конструкторов, которые допускают только действительные комбинации аргументов, обеспечит принудительное выполнение этого ограничения во времени компиляции. Тем не менее, вам также следует избегать даже столкновения с этим случаем (например, с несколькими классами, унаследованными от базы, где каждый класс выполняет исключительное поведение).

Kat
источник
+1 за упоминание взрыва перестановки с несколькими необязательными параметрами.
Jpsy