1. 概要

このクイックチュートリアルでは、 StrategyPatternの特殊なケースであるNullObjectPatternを見ていきます。 その目的と、実際に使用を検討する必要がある場合について説明します。

いつものように、簡単なコード例も提供します。

2. ヌルオブジェクトパターン

ほとんどのオブジェクト指向プログラミング言語では、null参照を使用することは許可されていません。 そのため、nullチェックを記述しなければならないことがよくあります。

Command cmd = getCommand();
if (cmd != null) {
    cmd.execute();
}

このようなifステートメントの数が多くなると、コードが醜くなり、読みにくくなり、エラーが発生しやすくなる場合があります。 これは、Nullオブジェクトパターンが役立つ場合があります。

Nullオブジェクトパターンの目的は、その種のnullチェックを最小限に抑えることです。代わりに、nullの動作を識別し、クライアントコードで期待されるタイプにカプセル化できます。 多くの場合、そのような中立的なロジックは非常に単純です–何もしません。 このようにして、null参照の特別な処理を処理する必要がなくなりました。

nullオブジェクトは、実際にはより高度なビジネスロジックを含む特定のタイプの他のインスタンスを処理するのと同じように処理できます。 その結果、クライアントコードはよりクリーンなままになります。

nullオブジェクトには状態があってはならないため、同一のインスタンスを複数回作成する必要はありません。 したがって、nullオブジェクトをシングルトンとして実装することがよくあります。

3. ヌルオブジェクトパターンのUML図

パターンを視覚的に見てみましょう。

ご覧のとおり、次の参加者を特定できます。

  • ClientにはAbstractObjectのインスタンスが必要です
  • AbstractObject は、 Client が期待するコントラクトを定義します。これには、実装クラスの共有ロジックが含まれる場合もあります。
  • RealObjectAbstractObjectを実装し、実際の動作を提供します
  • NullObjectAbstractObjectを実装し、ニュートラルな動作を提供します

4. 実装

理論が明確になったので、例を見てみましょう。

メッセージルーターアプリケーションがあると想像してください。 各メッセージには、有効な優先度が割り当てられている必要があります。 私たちのシステムは、優先度の高いメッセージをSMSゲートウェイにルーティングすることになっていますが、優先度が中程度のメッセージはJMSキューにルーティングする必要があります。

ただし、場合によっては、「未定義」または空の優先度のメッセージがアプリケーションに届くことがあります。 このようなメッセージは、以降の処理から破棄する必要があります。

まず、Routerインターフェースを作成します。

public interface Router {
    void route(Message msg);
}

次に、上記のインターフェイスの2つの実装を作成しましょう。1つはSMSゲートウェイへのルーティングを担当し、もう1つはメッセージをJMSキューにルーティングします。

public class SmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}
public class JmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}

最後に、 nullオブジェクトを実装しましょう:

public class NullRouter implements Router {
    @Override
    public void route(Message msg) {
        // do nothing
    }
}

これで、すべてのピースをまとめる準備が整いました。 サンプルのクライアントコードがどのように見えるかを見てみましょう。

public class RoutingHandler {
    public void handle(Iterable<Message> messages) {
        for (Message msg : messages) {
            Router router = RouterFactory.getRouterForMessage(msg);
            router.route(msg);
        }
    }
}

ご覧のとおり、すべてのRouterオブジェクトを同じように扱います。 RouterFactoryから返される実装に関係なく。これにより、コードをクリーンで読みやすくすることができます。

5. Nullオブジェクトパターンを使用する場合

クライアントが実行をスキップするかデフォルトのアクションを実行するためだけにnullをチェックする場合は、Nullオブジェクトパターンを使用する必要があります。このような場合、ニュートラルロジックをnullオブジェクト内にカプセル化し、それをnull値の代わりにクライアント。 このようにして、特定のインスタンスがnullであるかどうかをクライアントのコードが認識する必要がなくなります。

このようなアプローチは、 Tell-Don’t-Askなどの一般的なオブジェクト指向の原則に従います。

Null Object Patternをいつ使用すべきかをよりよく理解するために、次のように定義されたCustomerDaoインターフェイスを実装する必要があると想像してみましょう。

public interface CustomerDao {
    Collection<Customer> findByNameAndLastname(String name, String lastname);
    Customer getById(Long id);
}

ほとんどの開発者は、提供された検索条件に一致する顧客がいない場合 findByNameAndLastname()からCollections.emptyList()を返します。 これは、Nullオブジェクトパターンに従う非常に良い例です。

対照的に、 get ById()は、指定されたIDで顧客を返す必要があります。 このメソッドを呼び出す人は、特定の顧客エンティティを取得することを期待しています。 そのような顧客が存在しない場合は、明示的にnullを返して、指定されたIDに問題があることを通知する必要があります。 

他のすべてのパターンと同様に、 Null Object Pattern を盲目的に実装する前に、特定のユースケースを検討する必要があります。 そうしないと、コードに意図せずにバグを導入して、見つけるのが困難になる可能性があります。

6. 結論

この記事では、Null Object Patternとは何か、いつ使用できるかを学びました。 また、デザインパターンの簡単な例を実装しました。

いつものように、すべてのコードサンプルはGitHubで入手できます。