コスト削減した Null Object パターン

# Null Object パターンの目的
オブジェクトを要求してその結果が null かどうかの判定が繰り返し現れる場合に、その null 値をNull Object に置き換える事で、判定処理を行っている手続き的コードを除外する。

```php
if (is_null($user)) {
  $userName = '';
} else {
  $userName = $user->userName();
}
```

# 基本パターン
継承 ver.
![スクリーンショット 2021-08-21 9.45.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276559/522ae090-9cb8-6508-ded2-451a980a0f13.png)


インターフェース ver.
![スクリーンショット 2021-08-21 9.45.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276559/87ceffc5-d750-1b6b-8e5d-fe5abcaa0d15.png)


## 例（継承 ver.）
```php
<?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 判定を行う事になる。

```php
// my ページでユーザが取れない場合はエラー
if ($user->isNull()) {
  // エラー処理、エラー画面表示
}
```


## 注意点
Null Objectクラスをクラス毎に作成するのにコストがかかるため、得られる効果を考慮して導入するかどうかの判断が必要。

# 応用パターン
Null Object クラスを用意せず、Nullの場合の値をクラス内に定義する事で Null Objectパターンを実現する。
これによって、Null Object クラスの作成コストを減らすことができる。

## 例
```php
<?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 と区別したいケースは想定しづらい。
もし応用パターンが不適な場合が出たら、基本パターンでやることを考える。


# 参考
[新装版 リファクタリング―既存のコードを安全に改善する](https://www.amazon.co.jp/%E3%83%AA%E3%83%95%E3%82%A1%E3%82%AF%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0%E2%80%95%E6%97%A2%E5%AD%98%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E5%AE%89%E5%85%A8%E3%81%AB%E6%94%B9%E5%96%84%E3%81%99%E3%82%8B%E2%80%95-OBJECT-TECHNOLOGY-Martin-Fowler/dp/427405019X)
[アジャイルソフトウェア開発の奥義 第2版 オブジェクト指向開発の神髄と匠の技](https://www.amazon.co.jp/%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9-%E7%AC%AC2%E7%89%88-%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB%84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80-%E3%83%AD%E3%83%90%E3%83%BC%E3%83%88%E3%83%BBC%E3%83%BB%E3%83%9E%E3%83%BC%E3%83%81%E3%83%B3/dp/4797347783/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=%E3%82%A2%E3%82%B8%E3%83%A3%E3%82%A4%E3%83%AB%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%A5%A5%E7%BE%A9+%E7%AC%AC2%E7%89%88+%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E9%96%8B%E7%99%BA%E3%81%AE%E7%A5%9E%E9%AB%84%E3%81%A8%E5%8C%A0%E3%81%AE%E6%8A%80&qid=1629364365&s=books&sr=1-1)