Необов’язкові параметри або перевантажені конструктори


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
KISS та YAGNI. У разі сумнівів реалізуйте і те, і інше. Через деякий час зробіть пошук посилань на обидва конструктори. Мене не здивувало б, якби одну з них ніколи не споживали зовнішньо. Або незначно споживається. Це щось легко знайти, виконуючи статичний аналіз коду.
Лаїв

1
Статичні конструктори (як Bitmap.FromFile) - також варіант
BlueRaja - Danny Pflughoeft

3
Зберігайте правило аналізу коду CA1026: Параметри за замовчуванням не слід використовувати на увазі.
Ден Ліонс

2
@Laiv Я щось пропускаю? Вони використовуються однаково і таким чином мають однаковий підпис. Ви не можете шукати посилання та сказати, про що думав абонент. Це звучить більше, як ви посилаєтесь на те, чи повинен ОП навіть мати цей другий аргумент, але це не те, про що запитують ОП (і вони, можливо, точно знають, що їм потрібно і те, і інше, тому запитують).
Кет

2
@Voo Компілятор для Openedge | Progress 10.2B їх ігнорує. Досить сильно вбили нашу стратегію зміни беззбиткових методів; натомість нам потрібно було використовувати перевантаження для того ж ефекту.
Брет

Відповіді:


24

Я віддаю перевагу декільком конструкторам над значеннями за замовчуванням, і особисто мені не подобається приклад ваших двох конструкторів, це має бути реалізовано по-різному.

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

Однак у ваших прикладах різниці між ними немає, оскільки навіть вторинний конструктор передає значення 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]і повідомити користувачів, що його буде видалено, давши їм час перейти до нової реалізації, не вмить порушуючи код.


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


12
З іншого боку, зробити занадто багато параметрів необов'язковим було б заплутаним ... Якщо чесно, занадто багато параметрів (незалежно від того, є вони необов'язковими чи ні) є заплутаними.
Енді

1
@DavidPacker Я згоден з вами, але скільки параметрів занадто багато - я думаю, інша історія ;-)
t3chb0t

2
Існує ще одна відмінність між конструктором, який опускає параметр, порівняно з конструктором, який має параметр за замовчуванням. У першому випадку абонент може явно уникати передачі параметра, тоді як в останньому випадку абонент не може - оскільки передача жодного параметра не є такою ж, як передача значення за замовчуванням. Якщо конструктору потрібно зробити це розрізнення, то два конструктори - єдиний шлях.
Гері Макгілл

1
Наприклад, припустимо, у мене є клас, який формує рядки, які я необов'язково можу побудувати з CultureInfoоб'єктом. Я вважаю за краще API, який дозволив CultureInfoнадавати параметр через додатковий параметр, але наполягав на тому, що якщо він буде наданий, то цього не повинно бути null. Таким чином випадкові nullзначення не трактуються неправильно як значення "немає".
Гері Макгілл

Я не впевнений, що розширюваність насправді є причиною проти необов'язкових параметрів; якщо вам потрібні параметри взагалі і коли-небудь змінювати їх кількість, вам доведеться створити новий конструктор і Obsoleteстарий, якщо ви хочете уникнути зривів, незалежно від того, чи є у вас додаткові параметри чи ні. З необов’язковими параметрами ви повинні, лише Obsoleteякщо ви вилучили один.
Герохтар

17

Зважаючи на те, що єдине, що ви робите в конструкторі - це просте завдання, рішення для одного конструктора може бути кращим вибором у вашому випадку. Інший конструктор не забезпечує додаткових функціональних можливостей, і з конструкції конструктора з двома параметрами зрозуміло, що другий аргумент не потрібно наводити.

Кілька конструкторів мають сенс, коли ви створюєте об'єкт з різних типів. У цьому випадку конструктори є заміною зовнішньої фабрики, оскільки вони обробляють вхідні параметри та форматують їх для правильного внутрішнього представлення властивостей класу, який будується. Однак у вашому випадку цього не відбувається, тож чому одного конструктора має бути більше ніж достатньо.


3

Я думаю, що є два різних випадки, для яких кожен підхід може світити.

По-перше, коли у нас є прості конструктори (як правило, на мій досвід), я б розглядав необов'язкові аргументи, щоб світити.

  1. Вони мінімізують код, який потрібно записати (і, таким чином, також, який потрібно прочитати).
  2. Ви можете переконатися, що документація знаходиться в одному місці і не повторюється (що погано, оскільки вона відкриває додаткову область, яка може застаріти).
  3. Якщо у вас є багато необов'язкових аргументів, ви можете уникнути дуже заплутаного набору комбінацій конструкторів. Хек, навіть із лише двома необов'язковими аргументами (не пов'язаними один з одним), якщо ви хочете роздільних, перевантажених конструкторів, вам доведеться мати 4 конструктори (версія без жодної, версія з кожним і версія з обома). Це, очевидно, не так масштабно.

Бую, є випадки, коли необов'язкові аргументи в конструкторах просто роблять речі явно більш заплутаними. Очевидний приклад - коли ці необов'язкові аргументи є виключними (тобто їх не можна використовувати разом). Перезавантаження конструкторів, які дозволяють лише дійсно дійсні комбінації аргументів, забезпечить виконання цього обмеження в часі. Зважаючи на це, ви також повинні уникати навіть стикатися з цим випадком (наприклад, з кількома класами, успадкованими від бази, де кожен клас виконує виключну поведінку).


+1 для згадування перестановного вибуху з кількома необов'язковими параметрами.
Jpsy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.