Не використовуйте жодного, якщо ви можете цього уникнути. Використовуйте заводську функцію, яка повертає тип опції, кортеж або виділений тип суми. Іншими словами, представляйте значення, яке може бути дефолтом, і відрізняється від гарантованого значення, яке не підлягає дефолту.
Спочатку давайте перелічимо desiderata, а потім подумаємо, як ми можемо це виразити кількома мовами (C ++, OCaml, Python)
- зловити небажане використання об'єкта за замовчуванням під час компіляції.
- зробіть очевидним, чи є задане значення потенційно дефолтним чи ні під час читання коду.
- виберіть наше розумне значення за замовчуванням, якщо це застосовно, один раз на тип . Однозначно не вибирайте потенційно різного за замовчуванням на кожному сайті виклику .
- полегшують інструменти статичного аналізу або людину,
grep
яка полює за потенційними помилками.
- Для деяких програм програма повинна продовжуватись нормально, якщо несподівано дано значення за замовчуванням. Для інших програм програма повинна негайно зупинитися, якщо їй задано значення за замовчуванням, в ідеалі інформативно.
Я думаю, що напруга між Null Object Pattern і нульовими покажчиками походить від (5). Якщо ми зможемо виявити помилки досить рано, (5) стає суперечливим.
Розглянемо цю мову за мовою:
C ++
На мою думку, клас C ++, як правило, може бути конструктований за замовчуванням, оскільки він полегшує взаємодію з бібліотеками та полегшує використання класу в контейнерах. Це також спрощує успадкування, оскільки вам не потрібно думати про те, до якого конструктора надкласового класу слід звертатися.
Однак це означає, що ви не можете точно знати, чи є тип типу MyClass
у "стані за замовчуванням" чи ні. Окрім того, щоб викласти в bool nonempty
поле чи подібне поле до поверхневої настройки за замовчуванням під час виконання, найкраще, що ви можете зробити, це створити нові екземпляри MyClass
таким чином, щоб змусити користувача перевірити це .
Я рекомендую використовувати заводську функцію, яка повертає a std::optional<MyClass>
, std::pair<bool, MyClass>
або посилання r-значення, std::unique_ptr<MyClass>
якщо це можливо.
Якщо ви хочете, щоб ваша заводська функція повертала якийсь стан "заповнювача", який відрізняється від побудованого за замовчуванням MyClass
, використовуйте клавішу a std::pair
і обов'язково документуйте, що ваша функція це робить.
Якщо заводська функція має унікальну назву, її легко grep
назвати і шукати неохайність. Однак це важко grep
для випадків, коли програміст мав використовувати заводську функцію, але цього не зробив .
OCaml
Якщо ви використовуєте таку мову, як OCaml, ви можете просто використовувати option
тип (у стандартній бібліотеці OCaml) або either
тип (не в стандартній бібліотеці, але легко прокрутити свою власну). Або defaultable
тип (я складаю термін defaultable
).
type 'a option =
| None
| Some of 'a
type ('a, 'b) either =
| Left of 'a
| Right of 'b
type 'a defaultable =
| Default of 'a
| Real of 'a
Як defaultable
показано вище, краще, ніж пара, тому що користувач повинен узгодити шаблон для вилучення 'a
та не може просто ігнорувати перший елемент пари.
Еквівалент defaultable
типу, як показано вище, може використовуватися в C ++, використовуючи std::variant
два екземпляри одного типу, але частини std::variant
API непридатні, якщо обидва типи однакові. Крім того, це дивне використання, std::variant
оскільки його конструктори типу не називаються.
Пітон
Ви все одно не отримаєте перевірку часу компіляції для Python. Але, динамічно вводячись, зазвичай немає обставин, коли для розміщення компілятора вам потрібен екземпляр-заповнювач місця.
Я рекомендую просто кинути виняток, коли вас змусять створити екземпляр за замовчуванням.
Якщо це не прийнятно, я б рекомендував створити файл, DefaultedMyClass
який успадковується MyClass
та повертається з фабричної функції. Це купує вам гнучкість з точки зору "витіснення" функціональності у випадках, що не відповідають вимогам.