コアポイント
仕様に完全に準拠していませんが、直交性は「優れたデザイン」の原則に基づいたソフトウェアシステムの本質であり、モジュールが互いに分離され、システムが少なくなると言えます。硬くて脆弱な問題が発生しやすい。もちろん、実際にこのプロセスを実行するよりも、直交システムの利点について話す方が簡単です。それでも、システム内で高度に分離されたコンポーネントを達成することは決してユートピアの概念ではありません。多型などのさまざまなプログラミングの概念により、柔軟なプログラムの設計により、その部分は実行時に切り替えることができ、その依存関係は具体的な実装ではなく抽象的な形式で表現できます。 「インターフェイス指向プログラミング」の古いモットーは、インフラストラクチャまたはアプリケーションロジックを実装しているかどうかにかかわらず、時間とともに広く採用されてきたと思います。ただし、ドメインモデルの領域に足を踏み入れると、状況は非常に異なります。率直に言って、これは予測可能なシナリオです。結局のところ、相互に関連したオブジェクトのネットワーク(明確に定義されたビジネスルールによって制限されているデータと動作を持つ)は、なぜ多型である必要があるのでしょうか?それ自体はあまり意味がありません。ただし、この規則にはいくつかの例外があり、最終的にはこの状況に適用される可能性があります。最初の例外は、仮想プロキシの使用です。これは、実際のドメインオブジェクト実装と同じインターフェイスです。別の例外は、いわゆる「ヌルケース」です。これは、操作が十分に満たされたエンティティを埋める代わりにnull値を割り当てるか、戻ることになる可能性がある特別なケースです。従来の非政治的アプローチでは、モデルのユーザーはこれらの「有害な」ヌル値をチェックし、条件を優雅に処理し、コード全体に条件付きステートメントが爆発する必要があります。幸いなことに、この一見紛らわしい状況は、問題のオブジェクトと同じインターフェイスを実装するドメインオブジェクトのマルチブランチ実装を作成するだけで簡単に解決できます。操作の実行中にugいnull値を繰り返し確認します。当然のことながら、このアプローチは、多型の利点を極端にもたらす空のオブジェクトと呼ばれる設計パターンです。この記事では、いくつかのケースでこのパターンの利点を示し、それらがどのように多型の方法に密接に結びついているかを示します。
非政治的条件を扱う
予想されるように、空のオブジェクトパターンの利点を示すときに試す方法はいくつかあります。私が見つけた特に簡単なアプローチは、汎用ファインダーからnull値を返すことになる可能性のあるデータマッパーを実装することです。 1つのユーザーエンティティのみで構成されるスケルトンドメインモデルを正常に作成したとします。インターフェイスとそのクラスは次のとおりです
<?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(); }
<?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; } }
<?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; } }
<?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(); }
クライアントコードから条件ステートメントを削除します
しかし、これは、空のオブジェクトパターンが多型が神の恵みである理由を示す場合に正確にあるため、心配する必要はありません。これらの迷惑な条件付きステートメントを完全に取り除きたい場合は、以前のユーザークラスの多型バージョンを実装できます。
<?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() { } }
MapperのcreateUser()メソッドは、Finderに渡されたIDが有効なユーザーを返さないときに空のユーザーを作成する責任があるため、小さな状態を隠します。それでも、この微妙なコストは、クライアントコードの作業を多くの繰り返しチェックを行うだけでなく、空のユーザーに対処しなければならないときに文句を言うことのないゆるい消費者にも変えます。
<?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; } }
<?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(); }
この多型アプローチの主な欠点は、無効なエンティティを扱うときにクラッシュしないため、使用するアプリケーションが緩すぎることです。最悪の場合、ユーザーインターフェイスにはいくつかの空白の行のみが表示されますが、本当にうるさいものは私たちをうんざりさせるものはありません。これは、初期のヌルーザークラスの現在の実装をスキャンするときに特に明白です。それが実現可能であっても、推奨されるものは言うまでもなく、その多型を変更せずに空のオブジェクトのロジックをカプセル化することもできます。空のオブジェクトは、いくつかの特別なケースでのみクライアントコードにさらされるべきデフォルトデータと動作をカプセル化するのに最適であるとさえ言えます。あなたが十分に野心的で、シンプルな空のユーザーオブジェクトでこのコンセプトを試したい場合、現在のNulluserクラスは次のようにリファクタリングできます:
<?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; } }
nulluserの拡張バージョンは、静かな前任者よりもわずかに表現力があります。ゲッターは、無効なユーザーを要求するときにいくつかのデフォルトメッセージを返すための基本的な実装を提供するためです。些細なことですが、この変更は、クライアントコードが空のユーザーを処理する方法にプラスの影響を与えます。今回は、少なくとも存在しないユーザーをストレージから抽出しようとすると問題が発生することを少なくとも知っているからです。これは良いブレークスルーであり、実際には空ではない空のオブジェクトを実装する方法を示すだけでなく、特定のニーズに基づいて関連するオブジェクト内でロジックを移動するのがどれほど簡単かを示します。
結論
空のオブジェクトを実装することは、特にPHPではOOP(多型など)のコア概念が大幅に過小評価されていることが厄介であると言う人もいるかもしれません。彼らはある程度正しいです。それにもかかわらず、信頼できるプログラミングの原則と設計パターンの徐々に採用されたもの、言語オブジェクトモデルが到達した成熟度のレベルは、着実に前進し、複雑で非現実的な概念と見なされた「高級品」の一部を使用し始めることです。少し前に必要なすべての根拠を提供します。 nullオブジェクトパターンはこのカテゴリに分類されますが、その実装は非常にシンプルでエレガントであるため、クライアントコードで重複したnullチェックをクリアするときに魅力的ではないことは困難です。 Fotoliaの写真
(スペースの制限のため、元のテキストのFAQ部分はここで省略されています。)以上がPHPマスター| The Null Object Pattern - Polymorphism in Domain Modelsの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。