ホームページ >バックエンド開発 >PHPチュートリアル >PHPマスター| The Null Object Pattern - Polymorphism in Domain Models

PHPマスター| The Null Object Pattern - Polymorphism in Domain Models

Christopher Nolan
Christopher Nolanオリジナル
2025-02-25 14:53:08444ブラウズ

PHP Master | The Null Object Pattern - Polymorphism in Domain Models

コアポイント

  • 空のオブジェクトパターンは、多型を使用して条件付きコードを削減し、コードをより簡潔でメンテナンスにしやすくするデザインパターンです。実際のオブジェクトを置き換えることができる非機能的オブジェクトを提供し、ヌル値チェックの必要性を排除します。
  • 空のオブジェクトモードは、ファクトリーモードの作成と戻りのファクトリーモードや、実行時にオブジェクトの動作を変更するポリシーモードなど、他の設計モードと組み合わせて使用​​できます。
  • 空のオブジェクトパターンの潜在的な欠点は、不要なオブジェクトの作成につながり、メモリの使用量を増やす可能性があることです。また、追加のクラスとインターフェイスが必要であるため、コードをより複雑にする場合があります。
  • 空のオブジェクトパターンを実装するには、実際のオブジェクトと同じインターフェイスを実装する空のオブジェクトクラスを作成する必要があります。この空のオブジェクトは、インターフェイス内のすべてのメソッドのデフォルトの実装を提供し、実際のオブジェクトを置き換えることができます。これにより、コードがより堅牢でエラーが発生しやすくなります。

仕様に完全に準拠していませんが、直交性は「優れたデザイン」の原則に基づいたソフトウェアシステムの本質であり、モジュールが互いに分離され、システムが少なくなると言えます。硬くて脆弱な問題が発生しやすい。もちろん、実際にこのプロセスを実行するよりも、直交システムの利点について話す方が簡単です。それでも、システム内で高度に分離されたコンポーネントを達成することは決してユートピアの概念ではありません。多型などのさまざまなプログラミングの概念により、柔軟なプログラムの設計により、その部分は実行時に切り替えることができ、その依存関係は具体的な実装ではなく抽象的な形式で表現できます。 「インターフェイス指向プログラミング」の古いモットーは、インフラストラクチャまたはアプリケーションロジックを実装しているかどうかにかかわらず、時間とともに広く採用されてきたと思います。ただし、ドメインモデルの領域に足を踏み入れると、状況は非常に異なります。率直に言って、これは予測可能なシナリオです。結局のところ、相互に関連したオブジェクトのネットワーク(明確に定義されたビジネスルールによって制限されているデータと動作を持つ)は、なぜ多型である必要があるのでしょうか?それ自体はあまり意味がありません。ただし、この規則にはいくつかの例外があり、最終的にはこの状況に適用される可能性があります。最初の例外は、仮想プロキシの使用です。これは、実際のドメインオブジェクト実装と同じインターフェイスです。別の例外は、いわゆる「ヌルケース」です。これは、操作が十分に満たされたエンティティを埋める代わりにnull値を割り当てるか、戻ることになる可能性がある特別なケースです。従来の非政治的アプローチでは、モデルのユーザーはこれらの「有害な」ヌル値をチェックし、条件を優雅に処理し、コード全体に条件付きステートメントが爆発する必要があります。幸いなことに、この一見紛らわしい状況は、問題のオブジェクトと同じインターフェイスを実装するドメインオブジェクトのマルチブランチ実装を作成するだけで簡単に解決できます。操作の実行中にugいnull値を繰り返し確認します。当然のことながら、このアプローチは、多型の利点を極端にもたらす空のオブジェクトと呼ばれる設計パターンです。この記事では、いくつかのケースでこのパターンの利点を示し、それらがどのように多型の方法に密接に結びついているかを示します。

非政治的条件を扱う

予想されるように、空のオブジェクトパターンの利点を示すときに試す方法はいくつかあります。私が見つけた特に簡単なアプローチは、汎用ファインダーからnull値を返すことになる可能性のあるデータマッパーを実装することです。 1つのユーザーエンティティのみで構成されるスケルトンドメインモデルを正常に作成したとします。インターフェイスとそのクラスは次のとおりです

<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}</code>
<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}</code>
ユーザークラスは、一部のユーザーデータと動作を定義するためにいくつかのミューテーター/アクセサーズを実装するリアクティブな構造です。この構築されたドメインクラスを使用すると、さらに一歩進んで、ドメインモデルとデータアクセスレイヤーを相互に分離する基本的なデータマッパーを定義できます。

<code class="language-php"><?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }

    private function createUser(array $row) {
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user;
    }
}</code>
最初に表示されるはずのことは、MapperのFetchByID()メソッドがブロック内のいたずらであることです。これは、データベース内のユーザーが指定されたIDと一致しない場合にnullを効果的に返すためです。明らかな理由で、この不器用な条件により、クライアントコードは、マッパーのファインダーを呼び出すたびにヌル値を確認するために一生懸命働かなければなりません。

<code class="language-php"><?php use LibraryLoaderAutoloader,
    LibraryDatabasePdoAdapter,
    ModelMapperUserMapper;

require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader;
$autoloader->register();

$adapter = new PdoAdapter("mysql:dbname=test", "myusername", "mypassword");

$userMapper = new UserMapper($adapter);

$user = $userMapper->fetchById(1);

if ($user !== null) {
    echo $user->getName() . " " . $user->getEmail();
}</code>
一見すると、問題はありません。1つの場所で確認してください。しかし、同じ線が複数のページコントローラーまたはサービスレイヤーに表示される場合、レンガの壁にぶつかりますか?あなたがそれを実現する前に、マッパーによって返された一見無邪気なヌルは、多くの反復条件を生み出します。

クライアントコードから条件ステートメントを削除します

しかし、これは、空のオブジェクトパターンが多型が神の恵みである理由を示す場合に正確にあるため、心配する必要はありません。これらの迷惑な条件付きステートメントを完全に取り除きたい場合は、以前のユーザークラスの多型バージョンを実装できます。

<code class="language-php"><?php namespace Model;

class NullUser implements UserInterface
{
    public function setId($id) { }
    public function getId() { }

    public function setName($name) { }
    public function getName() { }

    public function setEmail($email) { }
    public function getEmail() { }
}</code>
完全なエンティティクラスがあらゆる種類の装飾をパッケージ化することを期待している場合、おそらく非常に失望するでしょう。エンティティの「null」バージョンは対応するインターフェイスに準拠していますが、この方法は空のラッパーであり、実際の実装はありません。 Nulluserクラスの存在は明らかに私たちに賞賛に値するものをもたらすものではありませんが、それは私たちがすべての以前の条件付きステートメントをゴミ箱に投げることを可能にする簡潔な構造です。それがどのように実装されているかを見たいですか?まず、いくつかの事前処理を行い、データマッパーを再構築して、ファインダーが空の値ではなく空のユーザーオブジェクトを返すようにする必要があります。

MapperのcreateUser()メソッドは、Finderに渡されたIDが有効なユーザーを返さないときに空のユーザーを作成する責任があるため、小さな状態を隠します。それでも、この微妙なコストは、クライアントコードの作業を多くの繰り返しチェックを行うだけでなく、空のユーザーに対処しなければならないときに文句を言うことのないゆるい消費者にも変えます。
<code class="language-php"><?php namespace ModelMapper;
use LibraryDatabaseDatabaseAdapterInterface,
    ModelUser,
    ModelNullUser;

class UserMapper implements UserMapperInterface
{   
    private $adapter;

    public function __construct(DatabaseAdapterInterface $adapter) {
        $this->adapter = $adapter;
    }

    public function fetchById($id) {
        $this->adapter->select("users", array("id" => $id));
        return $this->createUser($this->adapter->fetch());
    }

    private function createUser($row) {
        if (!$row) {
            return new NullUser;
        }
        $user = new User($row["name"], $row["email"]);
        $user->setId($row["id"]);
        return $user; 
    }
}</code>
<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();
}</code>

この多型アプローチの主な欠点は、無効なエンティティを扱うときにクラッシュしないため、使用するアプリケーションが緩すぎることです。最悪の場合、ユーザーインターフェイスにはいくつかの空白の行のみが表示されますが、本当にうるさいものは私たちをうんざりさせるものはありません。これは、初期のヌルーザークラスの現在の実装をスキャンするときに特に明白です。それが実現可能であっても、推奨されるものは言うまでもなく、その多型を変更せずに空のオブジェクトのロジックをカプセル化することもできます。空のオブジェクトは、いくつかの特別なケースでのみクライアントコードにさらされるべきデフォルトデータと動作をカプセル化するのに最適であるとさえ言えます。あなたが十分に野心的で、シンプルな空のユーザーオブジェクトでこのコンセプトを試したい場合、現在のNulluserクラスは次のようにリファクタリングできます:

<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    private $id;
    private $name;
    private $email;

    public function __construct($name, $email) {
        $this->setName($name);
        $this->setEmail($email);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
              "The ID for this user is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = $name;
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }
}</code>

nulluserの拡張バージョンは、静かな前任者よりもわずかに表現力があります。ゲッターは、無効なユーザーを要求するときにいくつかのデフォルトメッセージを返すための基本的な実装を提供するためです。些細なことですが、この変更は、クライアントコードが空のユーザーを処理する方法にプラスの影響を与えます。今回は、少なくとも存在しないユーザーをストレージから抽出しようとすると問題が発生することを少なくとも知っているからです。これは良いブレークスルーであり、実際には空ではない空のオブジェクトを実装する方法を示すだけでなく、特定のニーズに基づいて関連するオブジェクト内でロジックを移動するのがどれほど簡単かを示します。

結論

空のオブジェクトを実装することは、特にPHPではOOP(多型など)のコア概念が大幅に過小評価されていることが厄介であると言う人もいるかもしれません。彼らはある程度正しいです。それにもかかわらず、信頼できるプログラミングの原則と設計パターンの徐々に採用されたもの、言語オブジェクトモデルが到達した成熟度のレベルは、着実に前進し、複雑で非現実的な概念と見なされた「高級品」の一部を使用し始めることです。少し前に必要なすべての根拠を提供します。 nullオブジェクトパターンはこのカテゴリに分類されますが、その実装は非常にシンプルでエレガントであるため、クライアントコードで重複したnullチェックをクリアするときに魅力的ではないことは困難です。 Fotoliaの写真

(スペースの制限のため、元のテキストのFAQ部分はここで省略されています。)

以上がPHPマスター| The Null Object Pattern - Polymorphism in Domain Modelsの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。