Lors de la conception d'un système, je suis souvent confronté au problème de l'utilisation d'un ensemble de modules (journalisation, accès à une base de données, etc.) utilisés par les autres modules. La question est de savoir comment puis-je fournir ces composants à d'autres composants. Deux réponses semblent possibles: injection de dépendance ou utilisation du modèle d’usine. Cependant les deux semblent faux
- Les usines facilitent les tests et ne permettent pas de permuter facilement les implémentations. Ils ne font pas non plus apparaître de dépendances (par exemple, vous examinez une méthode, oubliant qu’elle appelle une méthode qui appelle une méthode qui appelle une méthode qui utilise une base de données).
- L'injection de dépendance gonfle massivement les listes d'arguments des constructeurs et couvre certains aspects de votre code. La situation typique est celle où les constructeurs de plus de la moitié des classes ressemblent à ceci
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Voici un exemple typique de situation qui me pose problème: j'ai des classes d'exception, qui utilisent des descriptions d'erreur chargées à partir de la base de données, à l'aide d'une requête comportant un paramètre de paramètre de langue utilisateur, qui est un objet de session utilisateur. Donc, pour créer une nouvelle exception, j'ai besoin d'une description, qui nécessite une session de base de données et la session utilisateur. Je suis donc condamné à faire glisser tous ces objets dans toutes mes méthodes, au cas où je devrais lancer une exception.
Comment puis-je aborder un tel problème ??
Réponses:
Utilisez l'injection de dépendance, mais chaque fois que vos listes d'arguments de constructeur deviennent trop grandes, refactorisez-les à l'aide d'un service de façade . L'idée est de regrouper certains des arguments du constructeur, en introduisant une nouvelle abstraction.
Par exemple, vous pouvez introduire un nouveau type
SessionEnvironment
encapsulant unDBSessionProvider
, leUserSession
et le chargéDescriptions
. Pour savoir quelles abstractions ont le plus de sens, il faut connaître les détails de votre programme.Une question similaire a déjà été posée ici sur SO .
la source
À partir de là, il ne semble pas que vous compreniez bien la DI - l’idée est d’inverser le modèle d’instanciation d’objet dans une usine.
Votre problème spécifique semble être un problème de POO plus général. Pourquoi les objets ne peuvent-ils pas simplement lancer des exceptions normales non lisibles par un humain lors de leur exécution, puis avoir quelque chose avant l'attentat final qui attrape cette exception et, à ce moment-là, utilise les informations de session pour lancer une nouvelle exception plus jolie ?
Une autre approche serait d'avoir une fabrique d'exceptions, qui est transmise aux objets via leurs constructeurs. Au lieu de lancer une nouvelle exception, la classe peut jeter sur une méthode de l'usine (par exemple
throw PrettyExceptionFactory.createException(data)
.N'oubliez pas que vos objets, à l'exception de vos objets d'usine, ne doivent jamais utiliser l'
new
opérateur. Les exceptions constituent généralement un cas particulier, mais dans votre cas, elles peuvent constituer une exception!la source
Builder
motif) le dicte. Si vous transmettez des paramètres à votre constructeur parce que votre objet instancie d’autres objets, c’est un cas évident pour IoC.Vous avez déjà très bien énuméré les inconvénients du modèle d'usine statique, mais je ne suis pas tout à fait d'accord avec les inconvénients du modèle d'injection de dépendance:
Cette injection de dépendance vous oblige à écrire du code car chaque dépendance n’est pas un bogue, mais une fonctionnalité: elle vous oblige à réfléchir pour savoir si vous avez réellement besoin de ces dépendances, favorisant ainsi le couplage lâche. Dans votre exemple:
Non, vous n'êtes pas condamné. Pourquoi est-il de la responsabilité de la logique applicative de localiser vos messages d'erreur pour une session utilisateur particulière? Et si, ultérieurement, vous vouliez utiliser ce service métier à partir d'un programme batch (sans session utilisateur ...)? Ou que se passe-t-il si le message d'erreur ne doit pas être affiché à l'utilisateur actuellement connecté, mais à son superviseur (qui peut préférer une langue différente)? Ou si vous vouliez réutiliser la logique métier sur le client (qui n'a pas accès à une base de données ...)?
Il est évident que la localisation des messages dépend de ceux qui les consultent, c’est-à-dire que la couche présentation en est responsable. Par conséquent, je jetterais les exceptions ordinaires du service métier, qui portent un identifiant de message qui peut ensuite être consulté dans le gestionnaire d'exceptions de la couche de présentation, quelle que soit la source de message utilisée.
De cette façon, vous pouvez supprimer 3 dépendances inutiles (UserSession, ExceptionFactory et probablement des descriptions), rendant ainsi votre code à la fois plus simple et plus polyvalent.
En règle générale, je n'utiliserais que des usines statiques pour les éléments pour lesquels vous avez besoin d'un accès omniprésent et dont la disponibilité est garantie dans tous les environnements dans lesquels nous pourrions vouloir exécuter le code (comme la journalisation). Pour tout le reste, j'utiliserais une vieille injection de dépendance.
la source
Utilisez l'injection de dépendance. L'utilisation d'usines statiques est un emploi de l'
Service Locator
anti - modèle. Voir le travail précurseur de Martin Fowler ici - http://martinfowler.com/articles/injection.htmlSi vos arguments de constructeur deviennent trop volumineux et que vous n'utilisez pas un conteneur DI par tous les moyens, écrivez vos propres fabriques pour instanciation, ce qui permet de le configurer, soit par XML, soit en liant une implémentation à une interface.
la source
J'irais aussi bien avec Dependency Injection. Rappelez-vous que l’ID n’est pas seulement effectué par le biais de constructeurs, mais également par l’intermédiaire de Property setters. Par exemple, l'enregistreur pourrait être injecté en tant que propriété.
En outre, vous pouvez utiliser un conteneur IoC qui peut vous alléger le fardeau, par exemple en conservant les paramètres de constructeur comme requis par la logique de votre domaine au moment de l’exécution (conserver le constructeur de manière à révéler l’intention de dépendances de la classe et du domaine réel) et peut-être injecter d’autres classes d’aide dans les propriétés.
Vous voudrez peut-être aller plus loin avec Programmnig orienté aspect, qui est implémenté dans de nombreux cadres majeurs. Cela peut vous permettre d'intercepter (ou de "conseiller" d'utiliser la terminologie AspectJ) au constructeur de la classe et d'injecter les propriétés pertinentes, éventuellement avec un attribut spécial.
la source
Je ne suis pas tout à fait d'accord. Du moins pas en général.
Usine simple:
Injection simple:
Les deux extraits servent le même objectif, ils établissent un lien entre
IFoo
etFoo
. Tout le reste n'est que syntaxe.Le passage
Foo
àADifferentFoo
prend exactement autant d'effort dans l'un ou l'autre échantillon de code.J'ai entendu des gens dire que l'injection de dépendance permet d'utiliser différentes liaisons, mais que le même argument peut être avancé pour la création d'usines différentes. Choisir la bonne reliure est aussi complexe que choisir la bonne usine.
Les méthodes d'usine vous permettent par exemple d'utiliser
Foo
dans certains endroits etADifferentFoo
dans d'autres. Certains peuvent appeler cela bon (utile si vous en avez besoin), d'autres peuvent appeler cela mauvais (vous pouvez faire un travail à moitié assommé pour tout remplacer).Cependant, il n’est pas si difficile d’éviter cette ambiguïté si vous vous en tenez à une seule méthode qui revient, de
IFoo
sorte que vous ayez toujours une source unique. Si vous ne voulez pas vous tirer une balle dans le pied, ne tenez pas un pistolet chargé ou ne vous visez pas le pied.C'est pourquoi certaines personnes préfèrent extraire explicitement les dépendances dans le constructeur, comme ceci:
J'ai entendu les arguments pro (pas de constructeur gonflant), j'ai entendu les arguments con (l'utilisation du constructeur permet plus d'automatisation de DI).
Personnellement, alors que j'ai cédé à notre senior qui souhaite utiliser des arguments de constructeur, j'ai remarqué un problème avec la liste déroulante dans VS (haut à droite, pour parcourir les méthodes de la classe actuelle): les noms de méthodes disparaissaient de vue quand on des signatures de la méthode est plus longue que mon écran (=> le constructeur gonflé).
Sur le plan technique, je m'en fiche de toute façon. L'une ou l'autre option demande à peu près autant d'effort à taper. Et puisque vous utilisez DI, vous n’appellerez généralement pas un constructeur manuellement de toute façon. Mais le bogue de l'interface utilisateur de Visual Studio me fait préférer ne pas gonfler l'argument du constructeur.
En passant, l' injection de dépendance et les usines ne s'excluent pas mutuellement . J'ai eu des cas où, au lieu d'insérer une dépendance, j'ai inséré une fabrique générant des dépendances (NInject vous permet heureusement de l'utiliser
Func<IFoo>
, vous n'avez donc pas besoin de créer une classe de fabrique réelle).Les cas d'utilisation pour cela sont rares mais ils existent.
la source
Dans cet exemple fictif, une classe de fabrique est utilisée au moment de l'exécution pour déterminer le type d'objet de requête HTTP entrant à instancier, en fonction de la méthode de requête HTTP. L'usine elle-même reçoit une instance d'un conteneur d'injection de dépendance. Cela permet à l’usine de déterminer le temps d’exécution et de laisser le conteneur d’injection de dépendance gérer les dépendances. Chaque objet de requête HTTP entrant comporte au moins quatre dépendances (superglobales et autres objets).
Le code client pour une configuration de type MVC, dans une configuration centralisée
index.php
, pourrait ressembler à ce qui suit (validations omises).Sinon, vous pouvez supprimer la nature statique (et le mot clé) de la fabrique et permettre à un injecteur de dépendance de gérer le tout (d'où le constructeur mis en commentaire). Cependant, vous devrez changer certaines (pas les constantes) des références de membre de classe (
self
) en membres d'instance ($this
).la source