Maison > développement back-end > tutoriel php > Le principe de substitution de Liskov

Le principe de substitution de Liskov

William Shakespeare
Libérer: 2025-03-01 08:47:09
original
773 Les gens l'ont consulté

The Liskov Substitution Principle

Points de base

  • Le principe de substitution Liskov (LSP) est un concept clé dans la programmation orientée objet qui garantit que les sous-classes peuvent remplacer leurs abstractions de classe de base sans casser le contrat par le code client. Il maintient l'intégrité de la conception du système et est crucial pour la réutilisabilité du code.
  • Les méthodes d'écrasement dans les sous-classes, certaines exigences doivent être satisfaites: leur signature doit correspondre à la signature de la classe parent;
  • Les violations du LSP peuvent conduire à un comportement imparable et à des erreurs difficiles à suivre. Il rend également le code plus difficile à maintenir et à étendre, car l'hypothèse qu'une sous-classe peut remplacer sa superclasse n'est plus vraie.
  • La réécriture de la méthode ne viole pas toujours le LSP. Cependant, si la méthode réécrite modifie le comportement de la méthode d'origine d'une manière qui n'est pas attendue dans le contrat de superclasse, elle violera le LSP.
  • Pour s'assurer que le code est conforme à LSP, il est préférable de créer des sous-classes qui étendent (plutôt que de réécrire) leurs fonctions de classe de base. De plus, l'utilisation de la composition au lieu de l'héritage et de la mise en œuvre d'interfaces peut aider à créer des classes dérivées sans briser l'abstraction des conditions imposées par LSP.

Scène fictive: pirate et matrice

La conversation suivante vient d'une scène coupée de la trilogie matricielle:

Merpheus: Neo, je suis dans la matrice maintenant. Désolé de vous dire cette mauvaise nouvelle, mais notre programme PHP de suivi des agents a besoin d'une mise à jour rapide. Il utilise actuellement la méthode Query () de PDO (avec String) pour obtenir l'état de tous les agents matriciels de notre base de données, mais nous devons utiliser des requêtes de prétraitement à la place.

neo: sonne bien, Morpheus. Puis-je obtenir une copie du programme?

Merphes: pas de problème. Clone notre référentiel et consultez les fichiers agentmapper.php et index.php.

(Neo exécute certaines commandes Git, et le code suivant apparaît devant lui)

<?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();
        }
    }   
}
Copier après la connexion
Copier après la connexion
<?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>";
}
Copier après la connexion

Nio: Morpheus, je viens de recevoir les documents. J'ai sous-classé APD et remplacer sa méthode Query () afin qu'il puisse utiliser des requêtes prétraitées. En raison de mes superpuissances, je devrais pouvoir terminer ce travail très rapidement. reste calme.

(Le son du clavier de l'ordinateur résonne dans l'air)

Nio: Morpheus, la sous-classe est prête pour les tests. Vérifiez-le à tout moment.

(Murphys a fouillé rapidement sur son ordinateur portable et a vu la classe suivante)

<?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());
        }
    }
}
Copier après la connexion

Merphes: L'adaptateur a l'air bien. Je vais l'essayer tout de suite pour voir si notre agent Mapper est capable de suivre les agents actifs qui voyagent à travers la matrice. Bonne chance à moi.

(Murphys a hésité un instant, exécutant le fichier index.php précédent, cette fois en utilisant la classe PdoAdapter du chef-d'œuvre de Neo. Ensuite, un cri!)

Merpheus: Neo, je crois que vous êtes le "Sauveur"! Mais il y avait une terrible erreur fatale sur mon visage, et la nouvelle était la suivante:

<code>Catchable fatal error: Argument 2 passed to LibraryDatabasePdoAdapter::query() must be an array, integer given, called in path/to/AgentMapper on line (who cares?)</code>
Copier après la connexion

(un autre cri)

NEO: Qu'est-ce qui ne va pas? ! Qu'est-ce qui ne va pas? ! (plus de cris)

Merphes: Je ne sais vraiment pas. Oh, l'agent Smith vient me rattraper maintenant! (La communication a été soudainement interrompue. Le long silence a mis fin à la conversation, suggérant que Morpheus a été pris au dépourvu et a été gravement blessé par l'agent Smith.)

LSP ne représente pas les programmeurs paresseux et stupides

Inutile de dire que le dialogue ci-dessus est fictif, mais le problème est sans aucun doute vrai. Si Neo n'avait appris qu'une ou deux connaissances sur le principe de substitution de Liskov (LSP) comme un pirate qu'il était aussi célèbre qu'autrefois, l'agent Smith pouvait être retracé immédiatement. Plus important encore, Morpheus est protégé de la méchanceté de l'agent. C'était tellement dommage pour lui. Cependant, dans de nombreux cas, les développeurs de PHP pensent à LSP presque le même que l'opinion précédente de NEO: LSP n'est rien d'autre qu'un principe théorique puriste qui a peu d'application dans la pratique. Mais ils sont allés dans le mauvais sens. Même si la définition formelle du LSP est éblouissante (y compris moi), son noyau est d'éviter une hiérarchie de classes de façon irrégulière où les descendants se comportent très différemment des abstractions de classe de base qui utilisent le même contrat. En termes simples, LSP stipule que lors de la réécriture des méthodes dans les sous-classes, les exigences suivantes doivent être satisfaites:

  1. sa signature doit correspondre à la signature de la classe parent
  2. Les conditions préalables (ce qui accepte) doivent être identiques ou plus faibles
  3. Leurs conditions post-conditions (ce qui est attendu) doit être identique ou plus forte
  4. Exception (le cas échéant) doit être la même que le type d'exception lancé par la classe parent

Maintenant, n'hésitez pas à relire la liste par-dessus (ne vous inquiétez pas, j'attendrai) et vous espérez comprendre pourquoi cela a du sens. Pour en revenir à l'exemple, l'erreur fatale de Neo ne parvient pas à maintenir la signature de la méthode la même, brisant ainsi le contrat avec le code client. Pour résoudre ce problème, la méthode findall () du mappeur d'agent peut être réécrite avec certaines instructions conditionnelles (odeur de code évidente), comme indiqué ci-dessous:

<?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();
        }
    }   
}
Copier après la connexion
Copier après la connexion

Si vous êtes de bonne humeur, essayez la méthode de refactorisation et cela fonctionnera bien, que ce soit en utilisant un objet PDO natif ou une instance d'un adaptateur PDO. Je sais que cela semble rugueux, mais c'est juste une solution rapide et facile, et cela viole manifestement le principe de l'ouverture et de la fermeture. D'un autre côté, la méthode Query () de l'adaptateur peut être refactorisée pour correspondre à sa signature de la classe parent de réécriture. Mais ce faisant, toutes les autres conditions énoncées par le LSP doivent également être satisfaites. En bref, cela signifie que la réécriture de la méthode doit être effectuée avec prudence et ne peut être effectuée que pour des raisons très solides. Dans de nombreux cas d'utilisation, en supposant que l'interface ne peut pas être utilisée, il est préférable de créer une sous-classe qui ne fait que s'étendre (plutôt que de remplacer) sa fonctionnalité de classe de base. Dans le cas de l'adaptateur APD de Neo, cette approche fonctionnera parfaitement et ne cassera jamais le code client à aucun niveau. Comme je viens de le dire, il y a une solution plus efficace - mais plus radicale - qui profite de la mise en œuvre d'interfaces. Alors que les adaptateurs PDO précédents ont été créés par l'héritage et violaient indéniablement les préceptes du LSP, les inconvénients proviennent en fait de la façon dont la classe de mappeur de l'agent a été conçue à l'origine. En fait, cela dépend d'une implémentation d'adaptateur de base de données concrète de haut en bas, plutôt que d'un contrat défini par l'interface. Et une grande puissance OO a été dite depuis les temps anciens, c'est toujours une mauvaise chose. Alors, comment la solution ci-dessus sera-t-elle implémentée?

(le reste est similaire au texte d'entrée, et peut être ajusté et simplifié au besoin)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal