Null Object パターンの目的
オブジェクトを要求してその結果が null かどうかの判定が繰り返し現れる場合に、その null 値をNull Object に置き換える事で、判定処理を行っている手続き的コードを除外する。
if (is_null($user)) {
$userName = '';
} else {
$userName = $user->userName();
}
基本パターン
例(継承 ver.)
<?php
class User
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function isNull(): bool
{
return false;
}
public function userName(): string
{
return $this->name;
}
public static function nullObject(): NullUser
{
return new NullUser();
}
}
class NullUser extends User
{
public function __construct() {
parent::__construct('');
}
public function isNull(): bool
{
return true;
}
}
function displayUserName(User $user) {
echo $user->userName();
}
displayUserName(new User('tanaka')); // 'tanaka'
displayUserName(User::nullObject()); // ''
補足
クライアントが User と NullUser で同じ応答(今回だと userName の取得)を要求する場合は上記のようにできるが、異なる応答を要求する場合は依然として is_null 判定を行う事になる。
// my ページでユーザが取れない場合はエラー
if ($user->isNull()) {
// エラー処理、エラー画面表示
}
注意点
Null Objectクラスをクラス毎に作成するのにコストがかかるため、得られる効果を考慮して導入するかどうかの判断が必要。
応用パターン
Null Object クラスを用意せず、Nullの場合の値をクラス内に定義する事で Null Objectパターンを実現する。
これによって、Null Object クラスの作成コストを減らすことができる。
例
<?php
class User
{
private const NULL_VALUE = '';
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public static function nullObject(): self
{
return new self(self::NULL_VALUE);
}
public function equal(self $another): bool
{
reutrn $this == $another;
}
}
$user = new User('tanaka');
$nullUser = User::nullObject();
$user->userName(); // 'tanaka'
$nullUser->userName(); // ''
if ($user->equal(User::nullObject())) { // nullObject の判定は equal() で行う
// 特別処理
}
名前が空だけど存在するユーザを認めたい場合は Null Object と区別ができないためこのパターンは使えないが、実際には User 属性に Id などが入ることを考えると、全て空のユーザを Null Object と区別したいケースは想定しづらい。
もし応用パターンが不適な場合が出たら、基本パターンでやることを考える。
参考
新装版 リファクタリング―既存のコードを安全に改善する
アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技