リスコフ代替原理

William Shakespeare
William Shakespeareオリジナル
2025-03-01 08:47:09778ブラウズ

The Liskov Substitution Principle

コアポイント

  • リスコフ代替原理(LSP)は、クライアントコードで契約を破ることなく、サブクラスが基本クラスの抽象化を置き換えることができるようにするオブジェクト指向プログラミングの重要な概念です。システム設計の整合性を維持し、コードの再利用性に不可欠です。
  • サブクラスの上昇する場合、特定の要件を満たす必要があります。
  • LSP違反は、追跡が困難な止められない動作とエラーにつながる可能性があります。また、サブクラスがスーパークラスを置き換えることができるという仮定はもはや真実ではないため、コードの維持と拡張が難しくなります。
  • メソッド書き換えは、常にLSPに違反するとは限りません。ただし、書き換えられたメソッドが、スーパークラス契約では予想されない方法で元の方法の動作を変更すると、LSPに違反します。
  • コードがLSPに準拠することを確認するために、基本クラスの関数を拡張するのではなく、拡張するのではなく)サブクラスを作成することをお勧めします。さらに、継承の代わりに構成を使用してインターフェイスを実装することは、LSPによって課される条件の抽象化を破ることなく、派生クラスを作成するのに役立ちます。

架空のシーン:ハッカーとマトリックス

次の会話は、マトリックスの三部作のカットシーンから来ています:

メルフェウス:ネオ、私は今マトリックスにいます。この悪いニュースをお知らせしますが、エージェント追跡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は、怠zyで愚かなプログラマーを表していません

言うまでもなく、上記の対話は架空のものですが、問題は間違いなく真実です。ネオが、かつて彼がかつて有名だったハッカーのようにリスコフ代替原則(LSP)について1つか2つの知識しか学んだ場合、エージェント・スミスはすぐに追跡できました。最も重要なことは、モルフェウスはエージェントの悪意から保護されていることです。それは彼にとってとても残念でした。ただし、多くの場合、PHP開発者は、NEOの以前の意見とほぼ同じLSPについて考えています。LSPは、実際にはほとんど適用されない純粋な理論的原則に他なりません。しかし、彼らは間違った方法で行きました。 LSPの正式な定義は見事ですが(私を含む)、その中心は、同じ契約を使用する基本クラスの抽象化と子孫が非常に異なって振る舞う不明確に定義されたクラスの階層を避けることです。簡単に言えば、LSPは、サブクラスでメソッドを書き換えるとき、次の要件を満たす必要があることを規定しています。

その署名は、親クラスの署名と一致する必要があります

    前提条件(受け入れるもの)は同じか弱いか
  1. 彼らの事後条件(予想されること)は同じか強いか
  2. 例外(ある場合)は、親クラスによってスローされた例外タイプと同じでなければなりません
  3. さて、上記のリストをもう一度読んでください(心配しないでください、待ちます)。これが理にかなっている理由を理解したいと思います。例に戻ると、Neoの致命的なエラーは、メソッドの署名を同じに保つことができず、クライアントコードと契約を破ります。この問題を解決するために、エージェントマッパーのfindall()メソッドは、以下に示すように、いくつかの条件付きステートメント(明らかなコード臭)で書き直すことができます。
    <?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();
            }
        }   
    }

    気分が良い場合は、リファクタリング方法を試してみると、ネイティブPDOオブジェクトを使用するか、PDOアダプターのインスタンスを使用するかにかかわらず、うまく機能します。私はこれが荒いように聞こえることを知っていますが、それは迅速かつ簡単な修正であり、開閉の原則に露骨に違反しています。一方、AdapterのQuery()メソッドは、親クラスの書き換えの署名に一致するようにリファクタリングできます。しかし、そうすることで、LSPによって記載されている他のすべての条件も満たされるべきです。要するに、これは、メソッドの書き換えは注意して行うべきであり、非常に強い理由でのみ行うことができることを意味します。多くのユースケースでは、インターフェイスを使用できないと仮定すると、基本クラスの機能を(オーバーライドするのではなく)拡張するだけのサブクラスを作成する方が良いです。 NEOのPDOアダプターの場合、このアプローチは完全に機能し、クライアントコードをどのレベルでも破ることはありません。先ほど言ったように、インターフェイスの実装を活用するより効率的な、しかしより根本的なソリューションがあります。以前のPDOアダプターは継承を通じて作成され、LSPの教訓に間違いなく違反しましたが、実際には、エージェントマッパークラスが元々設計された方法から欠点があります。実際、インターフェイス定義の契約ではなく、具体的なデータベースアダプターの実装に依存します。そして、古代から大きなOO力が言われてきましたが、これは常に悪いことです。では、上記のソリューションはどのように実装されますか?

    (残りは入力テキストに似ており、必要に応じて調整および簡素化できます)

以上がリスコフ代替原理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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