第1章
はじめてのデザインパターン  

第2章
逆引きカタログ ロジック編

第3章
逆引きカタログ J2EE編

第4章
逆引きカタログ その他

第5章
デザインパターン適用の勘所

第4章 逆引きカタログ その他

Authors:Agata Toshitaka

nullオブジェクト

 
イントロダクション
パターン解説
具体的な例
まとめ
 

イントロダクション

 Nullオブジェクトという言葉を聞いて何を想像しますか? Nullという言葉から想像すると、何もできないオブジェクトというイメージが浮かびます。 そう感じられた方はビンゴです!
 Nullオブジェクトは、nullの代わりに”何の処理も行わない”オブジェクト(= Nullオブジェクト)を使用するパターンです。
TOP

パターン解説

 Javaではnullはデリケートな存在です。 nullが設定された変数のメソッドを呼び出すと、簡単にNullPointerExceptionが発生してしまいます。
 このため、オブジェクトがnullかそうでないかということを常に意識してコードを書く必要が出てきます。
 通常はif文でオブジェクトがnullかどうかを逐一チェックをして、NullPointerExceptionの発生を防ぐことになります。
 しかしこの方法ですと、オブジェクトがnullになる可能性のある箇所すべてにnullチェックが入ってしまいます。 チェック箇所が多くなってくると、if文だらけになってコードもわかりにくくなってきます。 この状態がnullチェック地獄です。
 Nullオブジェクトパターンをうまく適用すると、nullチェックのif文を一掃できます。
 Nullオブジェクトパターンではオブジェクトがnullである場合に、nullの代わりに”何の処理も行わない”Nullオブジェクトを使います。 これによりオブジェクトがnullである可能性がなくなり、nullチェックが不要になります。
 Nullオブジェクトパターンを使うとオブジェクトがnullであるかどうかを気にすることなく、nullチェックなしで安心してオブジェクトが使用できるようになります(図6〜7)。
図6 Nullオブジェクト適用前/適用後
図7 Nullオブジェクトパターンのイメージ
TOP

具体的な例

 それではより具体的な例を見てみましょう。サンプルは商品の割引価格を計算するコードです。
 割引価格には「会員の割引価格」「セール時の割引価格」「通常の割引価格」の3種類があります。 割引価格の種類を決定するのは、Mainクラス実行時のアプリケーション引数です。 Mainクラスは「java Main 価格 割引ロジック名(sale, member, 指定しないのいずれか)」の形式で実行します。
 例えば「java Main 1000 member」で実行すると会員割引価格となり、「java Main 1000 sale」で実行するとセール時の割引価格となります。 「java Main 1000」と指定がない場合は、通常の割引価格になります。
 各クラスの役割は次のようになります(図8〜9)。
図8 サンプルのクラス図
図9 サンプルのシーケンス図

◎DiscountLogic(リスト14

 割引計算のロジックのインタフェースです。各割引計算クラスは、リスト14-@のcalcDiscountPrice()メソッド を実装して作成します。

リスト14 DiscountLogic.java
/**
 * 割引ロジックのインタフェースです。
 */
public interface DiscountLogic {
	/**
	 * 割引価格を計算します。
	 */
	int calcDiscountPrice(int price); …@
}

◎MemberDiscountLogic(リスト15

 会員割引の価格の計算ロジッククラスです。会員割引では、1,000円以上の場合5%OFFになります。 1,000円未満では割引はありません。
リスト15 MemberDiscountLogic.java

/**
 * メンバー会員の割引ロジックです。
 */
public class MemberDiscountLogic implements DiscountLogic {

	public int calcDiscountPrice(int price) {
		if(price >= 1000) {
			return (int) (price * 0.95);
		} else {
			return price;
		}
	}
}

◎SaleDiscountLogic(リスト16

 セール時の割引価格の計算ロジッククラスです。一律5%OFFとなります。
リスト16 SaleDiscountLogic.java

/**
 * セール時の割引ロジックです。
 */
public class SaleDiscountLogic implements DiscountLogic {
	public int calcDiscountPrice(int price) {
		return (int) (price * 0.8);
	}
}

◎NullDiscountLogic(リスト17

 通常価格の計算ロジッククラスで、Nullオブジェクトの役割を果たします。 リスト17-@のように処理は何も行わず、受け取った価格をそのまま返却しています。
リスト17 NullDiscountLogic.java

/**
 * 通常の割引ロジック(割引なし)です。
 */
public class NullDiscountLogic implements DiscountLogic {
	public int calcDiscountPrice(int price) {
		return price; …@
	}
}

◎Main(リスト18

 メイン処理です。
 アプリケーション引数から適切な割引計算ロジックをリスト18-@のcreateDiscountLogic()メソッドで生成して、割引価格を計算しコンソールに出力します。 リスト18-Bでは、割引計算の種類が指定されていない場合でも必ずNullDiscountLogicオブジェクトが返却されます。 DiscountLogicがnullになることはありませんので、リスト18-AでDiscountLogicのメソッドを呼び出すときもnullチェックは不要になります。
リスト18 Main.java

/**
 * メインクラスです。
 * 割引ロジック名を省略すると通常価格になります。
 * java Main [価格] [割引ロジック名(sale|member)]
 */
public class Main {
	public static void main(String[] args) {
		int price = Integer.parseInt(args[0]);
		String type = args.length > 1 ? args[1] : null;
		DiscountLogic logic = createDiscountLogic(type); …@
		int discountPrice = logic.calcDisCountPrice(price); …A
		System.out.println("割引前の価格=" + price);
		System.out.println("割引後の価格=" + discountPrice);
		System.out.println(
		   "使用した割引ロジッククラス=" + logic.getClass().getName());
	}

	/**
	 * 割引ロジックを生成するシンプルなファクトリです。
	 */
	private static DiscountLogic createDiscountLogic(String type) {
		if ("sale".equals(type)) {
			return new SaleDiscountLogic();
		} else if ("member".equals(type)) {
			return new MemberDiscountLogic();
B
		} else {
			return new NullDiscountLogic();
		}
} }
TOP

まとめ

◎Nullオブジェクトのメリット

 Nullオブジェクトでは何も処理を行わないクラスを利用することでnullチェックをなくします。 nullチェックのためのif文が不要になることで、コードが簡潔に記述できるようになります。 nullチェック漏れによる不測のエラーもなくなります。

◎ストラテジパターンとの組み合わせ

 Nullオブジェクトはストラテジパターンと組み合わせて利用されることが多いです。 「具体的な例」でも割引計算ロジックを切り替えるために、共通のインタフェースDiscountLogicを定義していますので、ストラテジパターンと似たパターンであるといえます。

◎シングルトンパターンとの組み合わせ

 Nullオブジェクトはシングルトンパターンとして実装されることもあります。 Nullオブジェクトではインスタンスごとに振る舞いが変化しないので、インスタンスは全体で1個あれば十分だからです。 「具体的な例」のNullオブジェクトをシングルトンパターンに書き換えるとリスト19リスト20のようになります。
リスト19 NullDiscountLogic.java(シングルトンパターン版)

/**
 * メインクラスです。
 * 割引ロジック名を省略すると通常価格になります。
 * java Main [価格] [割引ロジック名(sale|member)]
 */
public class NullDiscountLogic implements DiscountLogic {
シングルトン化のために追加した部分
	// 唯一のインスタンス
	private static NullDiscountLogic instance = new NullDiscountLogic();

	// コンストラクタ。外部からのインスタンス化禁止。
	private NullDiscountLogic() {
	}

	// シングルトンなインスタンスの取得。
	public static NullDiscountLogic getInstance() {
		return instance;
	}
public int calcDiscountPrice(int price) { return price; } }
リスト20 Main.java(シングルトンパターン版)

/**
 * 通常の割引ロジック(割引なし)です。
 */
public class Main {

	(中略)

	private static DiscountLogic createDiscountLogic(String type) {
		if ("sale".equals(type)) {
			return new SaleDiscountLogic();
		} else if ("member".equals(type)) {
			return new MemberDiscountLogic();
		} else {
常に同じインスタンスが使用される
			return NullDiscountLogic.getInstance();
} } }
TOP