Points de base
Si vous deviez prendre une décision arbitraire «Salomonic» concernant la pertinence de chaque principe solide, je dirais que le principe de dépendance à l'inversion (DIP) est le plus sous-estimé. Alors que certains des concepts de base dans le domaine de la conception orientée objet sont difficiles à comprendre au début, comme la séparation des préoccupations et la mise en œuvre de la commutation, en revanche, des paradigmes plus intuitifs et plus clairs sont plus simples, tels que la programmation orientée vers l'interface. Malheureusement, la définition formelle de la DIP est entourée d'une malédiction / bénédiction ressemblant à une épée à double tranchant, ce qui fait souvent l'ignorer les programmeurs, car dans de nombreux cas, les gens par défaut du principe comme une autre déclaration de la «programmation orientée vers l'interface» susmentionnée:
À première vue, la déclaration ci-dessus semble évidente. Étant donné que personne n'est actuellement en désaccord que les systèmes construits sur une forte dépendance à l'égard des implémentations concrets sont un mauvais présage de mauvaise conception, il est tout à fait raisonnable de changer certaines abstractions. Cela nous ramènera donc au point de départ, pensant que l'objectif principal du DIP concerne la programmation orientée vers l'interface. En fait, le découplage des interfaces des implémentations n'est qu'une méthode semi-finie pour répondre aux exigences de ce principe. La partie manquante consiste à implémenter le processus d'inversion réel. Bien sûr, la question se pose: quel est le renversement? Traditionnellement, les systèmes sont toujours conçus pour fabriquer des composants de haut niveau (qu'ils soient des classes ou des routines de processus) dépendent de composants de bas niveau (détails). Par exemple, un module de journalisation peut avoir de fortes dépendances sur une série de journalistes spécifiques (enregistrant réellement des informations dans le système). Par conséquent, chaque fois que le protocole de l'enregistreur est modifié, ce schéma passe-t-il des effets secondaires bruyants à la couche supérieure, même si le protocole a été résumé. Cependant, la mise en œuvre de DIP aide dans une certaine mesure à atténuer ces ondulations en faisant en sorte que le module de journalisation ait un protocole, inversant ainsi le flux de dépendance global. Après l'inversion, les bûcherons devraient fidèlement respecter le protocole, donc s'il y a un changement à l'avenir, ils devraient changer en conséquence et s'adapter aux fluctuations du protocole. En bref, cela montre que la DIP est un peu plus compliquée dans les coulisses que de s'appuyer uniquement sur des interfaces standard - la mise en œuvre du découplage. Oui, il discute de la fabrication de modules de haut niveau et de haut niveau dépendant de l'abstraction, mais en même temps, les modules de haut niveau doivent avoir ces abstractions - un détail subtil mais pertinent qui ne peut pas être facilement négligé. Comme vous pouvez vous y attendre, une façon qui pourrait vous aider à comprendre ce que Dip couvre réellement à travers certains exemples de code pratiques. Donc, dans cet article, je vais créer quelques exemples afin que vous puissiez apprendre à profiter de ce principe solide lors du développement d'une application PHP.
Développer un module de stockage simple (le "I" manquant en DIP)
De nombreux développeurs, en particulier ceux qui détestent le PHP orienté objet, ont tendance à voir la trempette et d'autres principes solides comme des dogmes rigides, à contraire au pragmatisme inhérent à la langue. Je peux comprendre cette idée car il est difficile de trouver des exemples pratiques de PHP dans la nature qui présentent les véritables avantages du principe. Je n'essaie pas de me présenter en tant que programmeur éclairé (qui ne va pas bien), mais il est toujours utile de travailler dur pour un bon objectif et de démontrer d'un point de vue pratique sur la façon de mettre en œuvre la baisse dans des cas d'utilisation réels. Tout d'abord, considérez l'implémentation d'un module de stockage de fichiers simple. Ce module est responsable de la lecture et de l'écriture de données à partir d'un fichier cible spécifié. À un niveau très simplifié, les modules de la question peuvent être écrits comme ceci:
<?php namespace LibraryEncoderStrategy; class Serializer implements Serializable { protected $unserializeCallback; public function __construct($unserializeCallback = false) { $this->unserializeCallback = (boolean) $unserializeCallback; } public function getUnserializeCallback() { return $this->unserializeCallback; } public function serialize($data) { if (is_resource($data)) { throw new InvalidArgumentException( "PHP resources are not serializable."); } if (($data = serialize($data)) === false) { throw new RuntimeException( "Unable to serialize the supplied data."); } return $data; } public function unserialize($data) { if (!is_string($data) || empty($data)) { throw new InvalidArgumentException( "The data to be decoded must be a non-empty string."); } if ($this->unserializeCallback) { $callback = ini_get("unserialize_callback_func"); if (!function_exists($callback)) { throw new BadFunctionCallException( "The php.ini unserialize callback function is invalid."); } } if (($data = @unserialize($data)) === false) { throw new RuntimeException( "Unable to unserialize the supplied data."); } return $data; } }
<?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $serializer; protected $file; public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) { $this->serializer = $serializer; $this->setFile($file); } public function getSerializer() { return $this->serializer; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->serializer->serialize($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->serializer->unserialize( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }
Ce module est une structure assez simple composée de quelques composants de base. La première classe lit et écrit des données à partir du système de fichiers, et la deuxième classe est un sérialiseur PHP simple pour générer une représentation storable des données en interne. Ces composants d'échantillons exécutent bien leur entreprise isolément et peuvent être connectés comme celui-ci pour travailler de manière synchrone:
<?php use LibraryLoaderAutoloader, LibraryEncoderStrategySerializer, LibraryFileFileStorage; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader; $autoloader->register(); $fileStorage = new FileStorage(new Serializer); $fileStorage->write(new stdClass()); print_r($fileStorage->read()); $fileStorage->write(array("This", "is", "a", "sample", "array")); print_r($fileStorage->read()); $fileStorage->write("This is a sample string."); echo $fileStorage->read();
À première vue, le module présente un comportement assez décent étant donné que la fonctionnalité du module permet un stockage et une acquisition faciles de diverses données du système de fichiers. De plus, la classe FileStorage injecte une interface sérialisable dans le constructeur, s'appuie ainsi sur la flexibilité fournie par l'abstraction plutôt qu'une implémentation rigide en béton. Avec ces avantages, quels sont les problèmes avec ce module? Souvent, les premières impressions superficielles peuvent être délicates et vagues. Si vous regardez attentivement, non seulement FileStorage s'appuie réellement sur le sérialiseur, mais en raison de cette dépendance serrée, le stockage et l'extraction des données du fichier cible sont limités au mécanisme de sérialisation natif à l'aide de PHP. Que se passe-t-il si les données doivent être transmises en XML ou JSON à un service externe? Les modules bien conçus ne sont plus réutilisables. Triste mais réel! Cette situation soulève des questions intéressantes. D'abord et avant tout, FileStorage montre toujours une forte dépendance aux sérialiseurs de bas niveau, même si les protocoles qui les rendent interopérables sont déjà isolés de l'implémentation. Deuxièmement, le niveau d'universalité divulgué par le protocole du problème est très limité, et il est limité à échanger un sérialiseur à un autre. Dans ce cas, s'appuyer sur l'abstraction est une perception illusoire, et le véritable processus d'inversion encouragé par le DIP n'est jamais mis en œuvre. Certaines parties du module de fichier peuvent être refactorisées pour se conformer fidèlement aux exigences de DIP. Ce faisant, la classe FileStorage gagnera la propriété du protocole utilisé pour stocker et extraire des données de fichiers, ce qui se débarrassera ainsi de la dépendance des sérialiseurs de bas niveau et vous permettant de basculer entre plusieurs politiques de stockage au moment de l'exécution. Pour ce faire, vous obtiendrez en fait beaucoup de flexibilité gratuitement. Passons donc à autre chose et voyons comment convertir le module de stockage de fichiers en une structure vraiment conforme à la trempette.
Invertir la propriété du protocole et les interfaces et les implémentations de découplage (maintenant une utilisation complète du DIP)
Bien qu'il n'y ait pas beaucoup d'options, il existe encore des moyens d'inverser efficacement la propriété du protocole entre la classe FileStorage et ses collaborateurs de niveau inférieur tout en maintenant l'abstraction du protocole. Cependant, il existe une méthode très intuitive car elle repose sur une encapsulation naturelle de l'espace de noms PHP hors de la boîte. Pour traduire ce concept quelque peu insaisissable en code concret, le premier changement qui doit être apporté au module est de définir un protocole plus lâche pour enregistrer et récupérer les données de fichiers afin qu'elle puisse être facilement manipulée dans des formats autres que la sérialisation PHP. Une interface isolée rationalisée comme indiqué ci-dessous peut faire le travail gracieusement et simplement:
<?php namespace LibraryEncoderStrategy; class Serializer implements Serializable { protected $unserializeCallback; public function __construct($unserializeCallback = false) { $this->unserializeCallback = (boolean) $unserializeCallback; } public function getUnserializeCallback() { return $this->unserializeCallback; } public function serialize($data) { if (is_resource($data)) { throw new InvalidArgumentException( "PHP resources are not serializable."); } if (($data = serialize($data)) === false) { throw new RuntimeException( "Unable to serialize the supplied data."); } return $data; } public function unserialize($data) { if (!is_string($data) || empty($data)) { throw new InvalidArgumentException( "The data to be decoded must be a non-empty string."); } if ($this->unserializeCallback) { $callback = ini_get("unserialize_callback_func"); if (!function_exists($callback)) { throw new BadFunctionCallException( "The php.ini unserialize callback function is invalid."); } } if (($data = @unserialize($data)) === false) { throw new RuntimeException( "Unable to unserialize the supplied data."); } return $data; } }
l'existence d'OniterInterface ne semble pas avoir un impact profond sur la conception globale du module de fichier, mais il fait plus que ceux qui sont superficiellement promis. La première amélioration est la définition d'un protocole très général pour coder et décoder les données. La deuxième amélioration est aussi importante que la première, c'est-à-dire que la propriété du protocole appartient désormais à la classe FileStorage car l'interface existe dans l'espace de noms de la classe. En bref, nous avons réussi à faire en sorte que le codeur / décodeur de bas niveau non défini dépend de la filestorage de haut niveau simplement en écrivant une interface avec l'espace de noms correct. En bref, il s'agit du processus d'inversion réel qui plongeait les défenseurs de son voile académique. Bien sûr, si la classe FileStorage n'est pas modifiée pour être un implémentateur injectant l'interface précédente, alors l'inversion sera une tentative maladroite à mi-chemin, alors voici la version refactorisée:
<?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $serializer; protected $file; public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) { $this->serializer = $serializer; $this->setFile($file); } public function getSerializer() { return $this->serializer; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->serializer->serialize($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->serializer->unserialize( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }
Le filestorage déclare désormais explicitement la propriété du protocole de codage / décodage dans le constructeur, et la seule chose qui reste est de créer un ensemble spécifique d'encodeur / décodeurs de bas niveau qui vous permettent de gérer les données de fichiers en plusieurs formats. Le premier de ces composants n'est qu'une implémentation de refactorisation du sérialiseur PHP écrit précédemment:
<?php use LibraryLoaderAutoloader, LibraryEncoderStrategySerializer, LibraryFileFileStorage; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader; $autoloader->register(); $fileStorage = new FileStorage(new Serializer); $fileStorage->write(new stdClass()); print_r($fileStorage->read()); $fileStorage->write(array("This", "is", "a", "sample", "array")); print_r($fileStorage->read()); $fileStorage->write("This is a sample string."); echo $fileStorage->read();
Analyse La logique derrière le sérialiseur est définitivement redondante. Néanmoins, il convient de souligner qu'il repose désormais non seulement sur les abstractions d'encodage / décodage plus lâches, mais la propriété des abstractions est explicitement exposée au niveau de l'espace de noms. Encore une fois, nous pouvons aller plus loin et commencer à écrire plus d'encodeurs pour souligner les avantages de la baisse. Cela dit, voici un autre composant de bas niveau supplémentaire à écrire:
<?php namespace LibraryFile; interface EncoderInterface { public function encode($data); public function decode($data); }
Comme prévu, la logique sous-jacente derrière le codeur supplémentaire est souvent similaire au premier sérialiseur PHP, à l'exception de toute amélioration et variantes notables. De plus, ces composants sont conformes aux exigences imposées par DiP et sont donc conformes aux protocoles de codage / décodage défini dans l'espace de noms FileStorage. Étant donné que les composants de niveau supérieur et inférieur dans le module de fichier reposent sur l'abstraction et que l'encodeur a une dépendance claire de la classe de stockage de fichiers, nous pouvons affirmer en toute sécurité que le module se comporte en ligne avec la spécification DIP. De plus, l'exemple suivant montre comment combiner ces composants:
<?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "default.dat"; protected $encoder; protected $file; public function __construct(EncoderInterface $encoder, $file = self::DEFAULT_STORAGE_FILE) { $this->encoder = $encoder; $this->setFile($file); } public function getEncoder() { return $this->encoder; } public function setFile($file) { if (!is_file($file) || !is_readable($file)) { throw new InvalidArgumentException( "The supplied file is not readable or writable."); } $this->file = $file; return $this; } public function getFile() { return $this->file; } public function resetFile() { $this->file = self::DEFAULT_STORAGE_FILE; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->encoder->encode($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function read() { try { return $this->encoder->decode( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }
En dehors d'une subtilité simple que le module expose au code client, il est très utile pour expliquer les points clés et démontrer de manière assez instructive pourquoi les prédicats de DIP sont en fait plus largement que l'ancien paradigme "de programmation" orientée vers l'interface. Il décrit et spécifie explicitement l'inversion des dépendances et doit donc être mis en œuvre par différents mécanismes. L'espace de noms de PHP est un excellent moyen d'y parvenir sans trop de charge, bien que des méthodes traditionnelles comme définir des dispositions d'applications très structurées et hautement expressives puissent produire les mêmes résultats.
Conclusion
Souvent, les opinions basées sur l'expertise subjective sont souvent biaisées, et bien sûr, les opinions que j'ai exprimées au début de cet article ne font pas exception. Cependant, il y a une légère tendance à ignorer le principe de l'inversion de dépendance pour son homologue solide plus complexe, car il est facilement mal compris comme synonyme de l'abstraction de dépendance. De plus, certains programmeurs ont tendance à réagir intuitivement et à considérer le terme "inversion" comme une expression d'abréviation qui contrôle l'inversion, et bien que les deux soient liés les uns aux autres, il s'agit finalement d'un faux concept. Maintenant que vous connaissez le vrai sens de la baisse, assurez-vous de profiter de tous les avantages qu'il apporte, ce qui rendra sûrement votre application moins sensible aux problèmes de vulnérabilité et de rigidité qui pourraient survenir au fil du temps. Images de Kentoh / Shutterstock
Les questions fréquemment posées sur le principe de l'inversion de Reliance
Le principe d'inversion de dépendance (DIP) est un aspect clé du principe solide de la programmation orientée objet. Son objectif principal est de découpler le module logiciel. Cela signifie que le module de haut niveau qui fournit une logique complexe est séparé du module de bas niveau qui fournit des opérations de base. Ce faisant, les modifications des modules de bas niveau auront un impact minimal sur les modules de haut niveau, ce qui rend l'ensemble du système plus facile à gérer et à maintenir.
La programmation programmatique traditionnelle implique généralement des modules de haut niveau qui reposent sur des modules de bas niveau. Cela peut conduire à un système rigide où les modifications d'un module peuvent avoir un impact significatif sur les autres modules. La trempette, en revanche, inverse cette dépendance. Les modules de haut et de bas niveau reposent sur l'abstraction, ce qui favorise la flexibilité et rend le système plus adaptable aux changements.
Bien sûr, considérons un exemple de programme simple qui lit les données à partir d'un fichier et les traite. Dans les méthodes traditionnelles, les modules de traitement peuvent s'appuyer directement sur les modules de lecture de fichiers. Cependant, avec DIP, les deux modules s'appuieront sur une abstraction, comme l'interface "DataReader". Cela signifie que le module de traitement n'est pas lié directement au module de lecture de fichiers, et nous pouvons facilement passer à différentes sources de données (telles que les bases de données ou les services Web) sans modifier le module de traitement.
DIP peut apporter plusieurs avantages à votre code. Il favorise le découplage, ce qui rend votre système plus flexible et plus facile à modifier. Il améliore également la testabilité du code, car les dépendances peuvent être facilement moquées ou bouclées. En outre, il encourage de bonnes pratiques de conception telles que la programmation orientée vers l'interface plutôt que la programmation orientée implémentation.
Bien que la DIP présente de nombreux avantages, il peut également introduire la complexité, en particulier dans les grands systèmes où le nombre d'abstractions peut devenir difficile à gérer. Cela peut également conduire à plus d'écriture de code, car vous devez définir les interfaces et éventuellement créer d'autres classes pour les implémenter. Cependant, ces défis peuvent être atténués par une bonne conception et une bonne pratique architecturale.
DIP est le dernier principe de l'abréviation solide, mais elle est étroitement liée à d'autres principes. Par exemple, le principe de responsabilité unique (SRP) et le principe ouvert et proche (OCP) favorisent le découplage, qui est un aspect clé du DIP. Le principe de substitution de Richter (LSP) et le principe d'isolement d'interface (ISP) traitent de l'abstraction, qui est au cœur de la DIP.
Absolument. Bien que la DIP soit généralement discutée dans le contexte de Java et d'autres langues orientées objet, le principe lui-même est indépendant du langage. Vous pouvez appliquer des DIP, telles que des interfaces ou des classes abstraites dans n'importe quelle langue qui prend en charge l'abstraction.
Un bon point de départ consiste à trouver des zones dans votre code où les modules de haut niveau dépendent directement des modules de bas niveau. Déterminez si vous pouvez introduire des abstractions entre ces modules pour les découpler. N'oubliez pas que l'objectif n'est pas d'éliminer toutes les dépendances directes, mais de s'assurer que les dépendances visent l'abstraction, et non les implémentations concrètes.
DIP est principalement utilisé pour améliorer la structure et la maintenabilité du code, plutôt que ses performances. Cependant, en rendant votre code plus modulaire et plus facile à comprendre, il peut vous aider à identifier et à résoudre les goulots d'étranglement des performances plus efficacement.
Bien que les avantages de la DIP soient souvent plus évidents dans les grands systèmes complexes, il peut également être utile dans les petits projets. Même dans les petites bases de code, les modules de découplage peuvent rendre votre code plus facile à comprendre, tester et modifier.
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!