回避できる場合は、どちらも使用しないでください。オプションタイプ、タプル、または専用の合計タイプを返すファクトリ関数を使用します。つまり、デフォルトとならないことが保証されている値とは異なるタイプで、デフォルトとなる可能性がある値を表します。
最初に、desiderataをリストしてから、これをいくつかの言語(C ++、OCaml、Python)でどのように表現できるかを考えてみましょう。
- コンパイル時にデフォルトオブジェクトの不要な使用をキャッチします。
- コードの読み取り中に、指定された値が潜在的にデフォルトであるかどうかを明確にします。
- 該当する場合、タイプごとに1回、適切なデフォルト値を選択します。すべての呼び出しサイトで潜在的に異なるデフォルトを絶対に選ばないでください。
- 静的分析ツールまたは人間が
grep潜在的な間違いを簡単に探し出すことができるようにします。
- 一部のアプリケーションでは、予期しないデフォルト値が与えられた場合、プログラムは正常に続行します。他のアプリケーションの場合、デフォルト値が与えられた場合、理想的には有益な方法で、プログラムは直ちに停止する必要があります。
Null Object Patternとnullポインターの間の緊張は(5)に起因すると思います。ただし、エラーを十分早期に検出できれば、(5)は意味がなくなります。
この言語を言語ごとに考えてみましょう。
C ++
私の意見では、C ++クラスは、ライブラリとのやり取りを容易にし、コンテナ内でクラスを使用しやすくするため、通常はデフォルトで構築可能である必要があります。また、どのスーパークラスコンストラクターを呼び出すかを考える必要がないため、継承が簡素化されます。
ただし、これは、typeの値がMyClass「デフォルト状態」にあるかどうかを確実に知ることができないことを意味します。bool nonempty実行時にデフォルトの状態を表示するために、または同様のフィールドに入力することに加えて、ユーザーにチェックを強いる方法での新しいインスタンスを生成するMyClassことが最善です。
可能であればstd::optional<MyClass>、std::pair<bool, MyClass>またはへのr値参照を返すファクトリ関数を使用することをお勧めしますstd::unique_ptr<MyClass>。
ファクトリー関数がdefault-constructed 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上記のタイプと同等のものは、std::variant同じタイプの2つのインスタンスを持つa を使用してC ++で使用std::variantできますが、両方のタイプが同じ場合、APIの一部は使用できません。またstd::variant、その型コンストラクターには名前が付けられていないため、これは奇妙な使用法です。
Python
とにかく、Pythonのコンパイル時チェックは行われません。しかし、動的に型指定されるため、一般に、コンパイラーをなだめるために何らかの型のプレースホルダーインスタンスが必要な状況はありません。
デフォルトのインスタンスを作成せざるを得ない場合は、例外をスローすることをお勧めします。
それが受け入れられない場合は、ファクトリ関数からDefaultedMyClass継承しMyClassて返す関数を作成することをお勧めします。必要に応じて、デフォルトのインスタンスで「スタブアウト」機能に関して柔軟性が得られます。