Null Object パターン
null チェックの代わりに、何もしないオブジェクトを使って条件分岐を排除するデザインパターン
Null Object パターンとは
Null Object パターンは、null を返す代わりに「何もしない」振る舞いを持つオブジェクトを返すことで、呼び出し側の null チェックを不要にするデザインパターンである。Bobby Woolf が Pattern Languages of Program Design 3 (1998 年・Addison-Wesley) に寄稿した「Null Object」で定式化したパターンである。
Tony Hoare は 2009 年の講演で、自身が ALGOL W に null 参照を導入したことを「10 億ドルの過ち」と振り返っている。Null Object パターンは、null が引き起こす問題のうち「呼び出し側に分岐を強いる」部分を、設計レベルで消しに行く手段だ。
Before / After
取得関数が null を返し得る設計では、依存を 1 つ増やすたびに呼び出し側の分岐が 1 つ増える。
// ❌ null チェックがコード全体に散在
function processOrder(order: Order) {
const logger = getLogger();
if (logger !== null) logger.info('Processing started');
const metrics = getMetrics();
if (metrics !== null) metrics.increment('orders.processed');
const notifier = getNotifier();
if (notifier !== null) notifier.send('Order processed');
}
// ✅ Null Object パターン: null チェック不要
function processOrder(order: Order) {
const logger = getLogger(); // NullLogger or ConsoleLogger
const metrics = getMetrics(); // NullMetrics or DatadogMetrics
const notifier = getNotifier(); // NullNotifier or SlackNotifier
logger.info('Processing started');
metrics.increment('orders.processed');
notifier.send('Order processed');
}
実装
要点は、何もしない実装を本物と同じインターフェースで用意し、切り替えを生成箇所 1 か所に閉じ込めることだ。
interface Logger {
info(message: string): void;
error(message: string, error?: Error): void;
}
class ConsoleLogger implements Logger {
info(message: string) { console.log(`[INFO] ${message}`); }
error(message: string, err?: Error) { console.error(`[ERROR] ${message}`, err); }
}
class NullLogger implements Logger {
info(_message: string) { /* 何もしない */ }
error(_message: string, _err?: Error) { /* 何もしない */ }
}
// ファクトリ関数で切り替え
function createLogger(enabled: boolean): Logger {
return enabled ? new ConsoleLogger() : new NullLogger();
}
NullLogger はインターフェースを完全に実装するが、すべてのメソッドが何もしない。呼び出し側は Logger インターフェースだけを知っていればよく、実体が ConsoleLogger か NullLogger かを意識しない。
実務での活用パターン
テスト時のモック代替
テストで外部サービスへの通知を抑制する。
// テスト: 実際のメール送信を抑制
const service = new OrderService(
new InMemoryOrderRepo(),
new NullEmailSender(), // メールを送らない
new NullLogger(), // ログを出さない
);
Optional Chaining との違い
TypeScript の ?. (Optional Chaining) は null チェックを簡潔に書く構文だが、Null Object パターンとは目的が異なる。
| 手法 | 目的 | 適するケース |
|---|---|---|
Optional Chaining (?.) | null アクセスの安全化 | プロパティの参照 |
| Null Object パターン | 振る舞いのデフォルト化 | メソッド呼び出しの分岐排除 |
user?.address?.city は値の参照に適しているが、logger?.info('...') のように振る舞いを持つオブジェクトには Null Object パターンの方が適切だ。
注意点
デバッグが難しくなる
NullLogger を使うと、エラーが発生してもログが出ない。本番環境で NullLogger が使われていないか確認する仕組みが必要だ。
過度な適用
すべての null を Null Object に置き換える必要はない。「値が存在しない」ことに意味がある場合 (検索結果が 0 件など) は、null や空配列を返す方が適切だ。
判断の目安は、そのメソッドが戻り値を持つかどうかにある。副作用だけのメソッド (ログ出力・通知・計測) は「何もしない」が自然な既定値になる。一方、戻り値のあるメソッドで空文字や 0 を返す Null Object を作ると、呼び出し側が本物の値と区別できず、集計や表示に無言で混ざり込む。
Null Object vs Optional vs null チェック
3 つの方式を、書き忘れが実行時エラーになるか (安全性) と、分岐がどこに置かれるか (可読性) で比べる。
| 方式 | 安全性 | 可読性 |
|---|---|---|
| null チェック (if) | 書き忘れた箇所がそのまま実行時のエラーになる | 分岐が呼び出し側に散り、ネストが深くなる |
| Optional/Maybe | 中身を取り出す前に、有無の扱いを型が要求する | 取り出しの記述は挟まるが、分岐は 1 箇所に収まる |
| Null Object | 何もしない実装を返すため、呼び出し側に null が渡らない | 呼び出し側は分岐を書かずに済む |
実践的な知識は関連書籍でも得られる。
この記事は役に立ちましたか?
関連用語
デザインパターン
ソフトウェア設計で繰り返し現れる問題に対する再利用可能な解決策のカタログ
Null 安全性
null 参照エラーを型システムで防止し、null の可能性がある値を明示的に扱うプログラミング言語の機能
ガード節
関数の先頭で異常条件を早期にチェックし、ネストを浅く保つプログラミングパターン
ストラテジーパターン
処理のやり方を差し替え可能な部品として取り出し、呼び出す側を変えずに実行時に選べるようにするデザインパターン
インターフェース分離の原則
SOLID の I - クライアントが使わないメソッドへの依存を強制しない原則
Template Method パターン
アルゴリズムの骨格を親クラスで定義し、具体的なステップをサブクラスに委ねるデザインパターン