ホームページ >バックエンド開発 >PHPチュートリアル >リスコフ代替原理
コアポイント
架空のシーン:ハッカーとマトリックス
次の会話は、マトリックスの三部作のカットシーンから来ています:メルフェウス:ネオ、私は今マトリックスにいます。この悪いニュースをお知らせしますが、エージェント追跡PHPプログラムには簡単な更新が必要です。現在、PDOのQuery()メソッド(文字列付き)を使用して、データベースからすべてのマトリックスエージェントの状態を取得していますが、代わりにプリプロセシングクエリを使用する必要があります。
neo:いいですね、モルフェウス。プログラムのコピーを入手できますか?
メルフェス:問題ありません。リポジトリをクローンして、agentmapper.phpおよびindex.phpファイルをチェックしてください。
(NEOはいくつかのgitコマンドを実行すると、彼の前に次のコードが表示されます)
nio:モルフェウス、文書を手に入れました。 PDOをサブクラス化し、Query()メソッドをオーバーライドして、前処理クエリを使用できるようにします。私の超大国のため、私はこの仕事を非常に迅速に終えることができるはずです。冷静さを保つ。<?php namespace ModelMapper; class AgentMapper { protected $_adapter; protected $_table = "agents"; public function __construct(PDO $adapter) { $this->_adapter = $adapter; } public function findAll() { try { return $this->_adapter->query("SELECT * FROM " . $this->_table, PDO::FETCH_OBJ); } catch (Exception $e) { return array(); } } }
<?php use ModelMapperAgentMapper; // 一个 PSR-0 兼容的类加载器 require_once __DIR__ . "/Autoloader.php"; $autoloader = new Autoloader(); $autoloader->register(); $adapter = new PDO("mysql:dbname=Nebuchadnezzar", "morpheus", "aa26d7c557296a4e8d49b42c8615233a3443036d"); $agentMapper = new AgentMapper($adapter); $agents = $agentMapper->findAll(); foreach ($agents as $agent) { echo "Name: " . $agent->name . " - Status: " . $agent->status . "<br>"; }
(コンピューターのキーボードの音が空中に響き渡ります)nio:モルフェウス、サブクラスはテストの準備ができています。いつでもチェックしてください。
(マーフィーズはラップトップで迅速に検索し、次のクラスを見ました)
メルフェス:アダプターはよく見えます。エージェントマッパーがマトリックスを通過するアクティブなエージェントを追跡できるかどうかを確認するために、すぐに試してみます。私に頑張ってください。
(Murphysはしばらくheして、以前のindex.phpファイルを実行して、今回はNeoの傑作PdoAdapterクラスを使用しています。 メルフェウス:ネオ、あなたは「救い主」だと思います!しかし、私の顔にはひどい致命的な誤りがあり、ニュースは次のとおりでした:
<?php namespace LibraryDatabase; class PdoAdapter extends PDO { protected $_statement; public function __construct($dsn, $username = null, $password = null, array $driverOptions = array()) { // 检查是否传递了有效的 DSN if (!is_string($dsn) || empty($dsn)) { throw new InvalidArgumentException("The DSN must be a non-empty string."); } try { // 尝试创建一个有效的 PDO 对象并设置一些属性。 parent::__construct($dsn, $username, $password, $driverOptions); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); } catch (PDOException $e) { throw new RunTimeException($e->getMessage()); } } public function query($sql, array $parameters = array()) { try { $this->_statement = $this->prepare($sql); $this->_statement->execute($parameters); return $this->_statement->fetchAll(PDO::FETCH_OBJ); } catch (PDOException $e) { throw new RunTimeException($e->getMessage()); } } }(別の叫び声)
neo:何が問題なのですか? !何が悪かったの? ! (その他の叫び声) メルフェス:私は本当に知りません。ああ、エージェント・スミスは今私を捕まえに来ています! (コミュニケーションが突然中断されました。長い沈黙が会話を終了し、モルフェウスが不意を突かれ、エージェントスミスによって重傷を負ったことを示唆しました。
言うまでもなく、上記の対話は架空のものですが、問題は間違いなく真実です。ネオが、かつて彼がかつて有名だったハッカーのようにリスコフ代替原則(LSP)について1つか2つの知識しか学んだ場合、エージェント・スミスはすぐに追跡できました。最も重要なことは、モルフェウスはエージェントの悪意から保護されていることです。それは彼にとってとても残念でした。ただし、多くの場合、PHP開発者は、NEOの以前の意見とほぼ同じLSPについて考えています。LSPは、実際にはほとんど適用されない純粋な理論的原則に他なりません。しかし、彼らは間違った方法で行きました。 LSPの正式な定義は見事ですが(私を含む)、その中心は、同じ契約を使用する基本クラスの抽象化と子孫が非常に異なって振る舞う不明確に定義されたクラスの階層を避けることです。簡単に言えば、LSPは、サブクラスでメソッドを書き換えるとき、次の要件を満たす必要があることを規定しています。
その署名は、親クラスの署名と一致する必要があります 気分が良い場合は、リファクタリング方法を試してみると、ネイティブPDOオブジェクトを使用するか、PDOアダプターのインスタンスを使用するかにかかわらず、うまく機能します。私はこれが荒いように聞こえることを知っていますが、それは迅速かつ簡単な修正であり、開閉の原則に露骨に違反しています。一方、AdapterのQuery()メソッドは、親クラスの書き換えの署名に一致するようにリファクタリングできます。しかし、そうすることで、LSPによって記載されている他のすべての条件も満たされるべきです。要するに、これは、メソッドの書き換えは注意して行うべきであり、非常に強い理由でのみ行うことができることを意味します。多くのユースケースでは、インターフェイスを使用できないと仮定すると、基本クラスの機能を(オーバーライドするのではなく)拡張するだけのサブクラスを作成する方が良いです。 NEOのPDOアダプターの場合、このアプローチは完全に機能し、クライアントコードをどのレベルでも破ることはありません。先ほど言ったように、インターフェイスの実装を活用するより効率的な、しかしより根本的なソリューションがあります。以前のPDOアダプターは継承を通じて作成され、LSPの教訓に間違いなく違反しましたが、実際には、エージェントマッパークラスが元々設計された方法から欠点があります。実際、インターフェイス定義の契約ではなく、具体的なデータベースアダプターの実装に依存します。そして、古代から大きなOO力が言われてきましたが、これは常に悪いことです。では、上記のソリューションはどのように実装されますか? (残りは入力テキストに似ており、必要に応じて調整および簡素化できます)前提条件(受け入れるもの)は同じか弱いか
<?php namespace ModelMapper;
class AgentMapper
{
protected $_adapter;
protected $_table = "agents";
public function __construct(PDO $adapter) {
$this->_adapter = $adapter;
}
public function findAll() {
try {
return $this->_adapter->query("SELECT * FROM " . $this->_table, PDO::FETCH_OBJ);
}
catch (Exception $e) {
return array();
}
}
}
以上がリスコフ代替原理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。