Nullオブジェクトパターンの紹介と正体

追記: 現在は推奨していません。

デザインパターン「Nullオブジェクト」について解説します。加えて、後半でNullオブジェクトパターンに関する私見を述べます。

なぜ「Nullオブジェクト」が必要か

C や C# などのいくつかの言語に null という概念があります。null はしばしば「何もない」や「失敗」といった 特殊な状態 を表す値として、null でない値と混ぜて使われますが、null と非 null の差異は次のようなやっかいな状況の引き金となります。

null と非 null の違いの一つに、備えるメソッドの種類があります 1 。例えばプログラミング言語 C# では、null のメソッドを呼ぼうとすると、例外が投げられます。

// 何らかのクラス X
public class X
{
    // オーバーライド可能なメソッド
    public virtual void SomeMethod()
    {
        // ...
    }
}

public void ThrowNullReferenceException()
{
    // 型 X の変数 x を null にする。
    X x = null;
    // x (= null) のメソッドを呼ぶ。例外が投げられる。
    x.SomeMethod();
}

そのため、プログラムの処理中にエラーが起きると困る場合、 null のメソッドを呼んでしまわないようにする必要があります。

このような事情から、「null でない場合だけ、メソッドを実行する」(null の場合は何もしない)ようにしたいことがしばしばあります。

public void NotThrowNullReferenceException()
{
    // 型 X の変数 x を null にする。
    X x = null;
    // x が null でない場合だけ、メソッドを呼び出す。
    if (x != null)
    {
        x.SomeMethod();
    }
}

しかしメソッドを呼ぶ際にいちいち if 文を書いていると、 if 文が大量に発生してしまいます。コードが増えるだけならまだしも、if 文を書き忘れて、意図せぬ例外を投げてしまうおそれもあります。

このような状況を回避する方法の1つが、 Nullオブジェクト です。

Nullオブジェクトの作りかた

Null オブジェクトとは、次のように定義される、「null の代わりに使われるオブジェクト」のことです。

// X を継承した新しいクラス
public class NullX
    : X
{
    // 「何もしない」メソッドとしてオーバーライドする。
    public override void SomeMethod()
    {
    }
}

そして、型 X の変数には、null の代わりにこの NullX クラスのインスタンスを代入するようにします。これにより、変数が null ではないと分かるので、条件分岐を行わずとも安全にメソッドを呼び出せるようになります。

public void NotThrowNullReferenceException()
{
    // 型 X の変数 x を (null ではなく) NullX のインスタンスにする。
    X x = new NullX();

    // x は null でないと分かっているので、安心してメソッドを呼べる。
    x.SomeMethod();
}

めでたし、めでたし。

nullの発生源

(ここからは私見になります。)

ところで、そもそもなぜ、型 X の変数 (仮に x と呼ぶ) に null が入ってしまったのでしょうか。先ほどのサンプルでは、話を単純にするために x を使用する直前に X x = null; と書いていましたが、実際にはこんなコードはありえません。x == null になるには理由があるはずです。

C# の場合、理由は2つ考えられます:

  • x は初期化されなかった。(C# では、初期化されていない x の値は null になります。)
  • 誰かが xnull を代入した。あるいは、誰かが x の初期値として null を指定した。

前者の場合

前者の場合、「x が初期化されないまま使用されている」ことになります。

ここで確認する必要があるのは、「x が初期化されないまま使用されることがあると 開発者が理解している かどうか」です。理解していないのなら、 x == null で条件分岐するのではなく、 x が必ず初期化されるようにコードを修正するべきです。なぜなら、「なぜ x == null になるのか分からないけれど、とりあえず x == null の場合は何もしないようにしておく」という考えは、null のメソッドを呼び出すことで起こるはずだった例外を「握りつぶす」ことと等価だからです。

一方、理解しているのであれば、それは x の初期化時に null を設定しているのと同じことですので、後者の場合に含まれると考えてよいでしょう。

後者の場合

後者の場合、null を設定した人は、どういう意図をもってそれをしたのでしょうか。言い換えると、x == null である場合に、コードがどう動くことを期待しているのでしょうか。

もし「x == null の場合には、x を使用するコードは、何もしないべき」だと考えていたなら、そもそも xnull を設定するべきではありませんでした。なぜなら、 null は「何もしない」を意味するものではない からです。本来の意図を正しく表現するには、x に「何もしないという挙動を持つインスタンス」を設定すべきなのです。

// X の派生クラスで、「何もしないという挙動」を持つもの
public class NullX
    : X
{
    public override void SomeMethod()
    {
        // 何もしない。
    }
}

これがさきほどの NullX クラスです。名前に null とついてはいますが、null とは異なり、実体を持つ何かです。

まとめ

  • 問題: null のメソッドを呼んでしまわないように、条件分岐を書きすぎたり書き忘れたりしてしまう。
  • 解決策: null を使わない。変数は適切なインスタンスで初期化する。

参考

  • NullObjectパターン - Qiita

    Ruby によるサンプルコードと、Nullオブジェクトパターンの参考になるリンクが記載されています。


  1. null の性質は言語によって異なります。例えば null に対して任意のメソッドを呼べるような言語に対して、本記事の話は無意味です。 ↩︎

関連記事