Ha, quel déguisement gracieux du localisateur de services sous DI. Cela pourrait même ressembler à un DI! :-)
Ceci est le premier commentaire de ma publication précédente, Dependency Injection, JavaScript et ES6 Modules . Merci au collègue symbix 'u pour ce commentaire, tk. c'est lui qui est devenu la cause de l'immersion dans les subtilités de la différence entre l'un et l'autre. Sous la coupe ma réponse à la question dans le titre.

(KDPV n'a pas beaucoup de sens et est principalement destiné à l'identification visuelle de cette publication, entre autres)
Pour commencer, nous traiterons des définitions ( certains des exemples de code que je fournirai en PHP, certains en JS, parce que ces deux langages sont actuellement dans mes bagages actifs, et certains dans n'importe quel autre, parce que j'ai volé ces exemples sur Internet ).
Injection de dépendance
DI en un mot - au lieu du module require / import, vous injectez la dépendance via le paramètre constructeur (ou setter de propriété). Autrement dit, derrière ce grand mot se trouve le simple «passer les dépendances de classe via les paramètres du constructeur».
Ce commentaire est le collègue de risedphantom qui transmet assez précisément l'essence du phénomène. Pour faciliter la compréhension du code par le développeur, toutes les dépendances sont décrites explicitement - sous la forme de paramètres de constructeur (généralement, mais pas toujours):
class MainClass { public function __construct($dep1, $dep2, $dep3) {} }
C’est tout. DI - il est à ce sujet. Les dépendances dont nous avons besoin sont fournies par quelqu'un là-bas. Et où est-ce que «quelqu'un là-bas» les emmène - DI s'en fiche.
Lors de l'analyse du code, il est important de comprendre ce qui est exactement «interne» pour lui (et ce que nous pouvons changer en toute sécurité), et ce qui vient / dépasse la responsabilité de ce code. C'est ce qui excite DI. Pourquoi les dépendances passent-elles principalement par le constructeur, et non par les setters? Parce que c'est plus pratique pour le développeur - il voit immédiatement toutes les dépendances du code analysé en un seul endroit. Nous sommes habitués à penser que DI est quelque chose au niveau de la classe, mais les paramètres de fonction / méthode sont également DI:
function f(dep1, dep2, dep3) {}
Un constructeur est juste une méthode spéciale parmi toutes les autres.
Localisateur de services
Service Locator est un anti-modèle bien connu. Que fait-il? Fournit l'accès à un objet à d'autres objets. Voici une interface typique pour un tel localisateur:
interface ServiceLocator { public function addInstance(string $class, Service $service); public function addClass(string $class, array $params); public function has(string $interface): bool; public function get(string $class): Service; }
Un localisateur est un conteneur dans lequel vous pouvez placer des objets prêts à l'emploi (ou définir les règles de leur création), puis accéder à ces objets. Pour un localisateur, dans l'ensemble, peu importe la façon dont les objets y apparaissent - ils sont explicitement créés de l'extérieur et placés dans le conteneur ou créés par le conteneur lui-même selon les règles données.
Le localisateur de services est responsable du stockage des objets et de leur accès. C’est tout.
Conteneur d'injection de dépendance
Qu'est-ce qu'un conteneur DI? Selon la relecture indépendante du contenu des " Principes, pratiques et modèles d'injection de dépendance ": "une bibliothèque de logiciels qui fournit des fonctionnalités DI et permet d'automatiser de nombreuses tâches impliquées dans la composition d'objets, l'interception et la gestion à vie. Les conteneurs DI sont également appelés inversion de Conteneurs de contrôle (IoC). (§3.2.2) "
Autrement dit, le conteneur DI est principalement responsable de la création d'objets, mais seulement dans le second - de leur stockage. Un conteneur DI peut même ne pas stocker d'objets créés du tout, si l'application elle-même n'a pas besoin d'objets communs à l'ensemble de l'application (comme une configuration ou un enregistreur).
Voici, par exemple, l'interface du conteneur Symfony DI:
interface ContainerInterface extends PsrContainerInterface { public function set(string $id, ?object $service); public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); public function has($id); public function initialized(string $id); public function getParameter(string $name); public function hasParameter(string $name); public function setParameter(string $name, $value); }
Si nécessaire, vous pouvez très facilement vous convaincre que l'interface du conteneur DI est très similaire à l'interface des services de localisation - le même get
, has
et set
/ add
.
Pourquoi le Service Locator est-il mauvais?
Mais rien. Ce n'est pas le modèle lui-même qui est mauvais, mais la façon dont il est parfois utilisé. En particulier, il existe une opinion selon laquelle «le localisateur de services viole l'encapsulation dans les langues typées statiquement, car ce modèle n'exprime pas clairement les conditions préalables ». Et un exemple de violation:
public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
C'est tellement mauvais, mais bon - comme ceci:
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
Hé, nous venons de IOrderValidator
côté les parenthèses du moment de la création des objets eux-mêmes avec les IOrderShipper
IOrderValidator
et IOrderShipper
! Il est possible que dans cette application ailleurs, il y ait quelque chose comme ce code:
var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order);
Racine de composition
En parlant d'Injection de Dépendance, nous ne pouvons nous empêcher d'arriver à un concept tel que la Racine de Composition (ci-après je l'appellerai le "Point d'Assemblage"). Il s'agit du même « quelqu'un » à qui les responsabilités de création de dépendances et de leur mise en œuvre sont déléguées.
Au point d'assemblage, toutes les dépendances sont définies explicitement, les objets correspondants sont créés et implémentés là où ils attendent. Ici, les différences entre le localisateur de services et le conteneur DI sont minimes. Cela et un autre permettent de créer de nouveaux objets, de stocker les objets créés, d'extraire des objets stockés. Je m'engagerais même à affirmer qu'il n'y a pas ici de différences fondamentales entre eux.
La principale différence
Et où les différences entre le conteneur DI et le conteneur du localisateur de services sont-elles les plus évidentes? A tout autre endroit, et notamment avec un accès statique au conteneur:
$obj = Container::getInstance()->get($objId);
Ça y est. Sous cette forme, n'importe où (sauf s'il s'agit d'un point d'assemblage, mais que l'utilisation de l'accès statique y est très douteuse), tout conteneur devient un anti-modèle appelé Service Locator.
Un collègue de VolCh a répondu brièvement et succinctement à ma question :
Et en quoi pensez-vous que la vraie DI diffère d'un localisateur de service déguisé en DI?
comme ceci:
Accès aux conteneurs
En fait, toute cette publication n'est qu'un déploiement plus détaillé de sa réponse ici.
Utilisation légale du conteneur
Ainsi, qu'un conteneur soit un conteneur DI ou un conteneur pour le localisateur de services dépend beaucoup de l'endroit et de la façon dont nous l'utilisons. Comme je l'ai dit plus haut, au point de rassemblement, la différence entre les types de conteneurs disparaît. Mais que passons-nous si le conteneur lui-même est une dépendance pour une classe?
class Foo { public function __construct(Container $container){} }
Encore une fois, tout dépend de la façon dont nous utilisons le conteneur. Voici deux exemples, dont l'un correspond exactement au conteneur du localisateur de services et est un anti-modèle, tandis que l'autre a droit à la vie sous certaines conditions.
class Foo { private $dep; public function __construct(Container $container) { $this->dep = $container->get('depId'); } }
class Foo { private $container; public function __construct(Container $container) { $this->container = $container; } public function initModules($modules) { foreach ($modules as $initClassId) { $init= $this->container->get($initClassId); $init->exec(); } } }
Résumé
En fait, tout est un peu plus compliqué, le conteneur DI doit avoir plus de fonctionnalités que le localisateur de services, mais l'essence de cette publication est que même le conteneur DI le plus sophistiqué peut être facilement utilisé comme localisateur de services, mais pour utiliser le localisateur de services comme conteneur DI - vous devez essayer.
PS
Je ne peux pas m'empêcher de fournir des liens vers le blog de programmation de Sergei Teplyakov - ces questions y sont très bien couvertes.