Не використовуйте жодного, якщо ви можете цього уникнути. Використовуйте заводську функцію, яка повертає тип опції, кортеж або виділений тип суми. Іншими словами, представляйте значення, яке може бути дефолтом, і відрізняється від гарантованого значення, яке не підлягає дефолту.
Спочатку давайте перелічимо 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::variantAPI непридатні, якщо обидва типи однакові. Крім того, це дивне використання, std::variantоскільки його конструктори типу не називаються.
Пітон
Ви все одно не отримаєте перевірку часу компіляції для Python. Але, динамічно вводячись, зазвичай немає обставин, коли для розміщення компілятора вам потрібен екземпляр-заповнювач місця.
Я рекомендую просто кинути виняток, коли вас змусять створити екземпляр за замовчуванням.
Якщо це не прийнятно, я б рекомендував створити файл, DefaultedMyClassякий успадковується MyClassта повертається з фабричної функції. Це купує вам гнучкість з точки зору "витіснення" функціональності у випадках, що не відповідають вимогам.