Supposons que 1001 clients construisent directement leurs dépendances plutôt que d'accepter les injections. La refactorisation du 1001 n'est pas une option selon notre patron. En fait, nous ne sommes même pas autorisés à accéder à leur source, uniquement aux fichiers de classe.
Ce que nous sommes censés faire, c'est «moderniser» le système que ces 1001 clients traversent. Nous pouvons refactoriser tout ce que nous aimons. Les dépendances font partie de ce système. Et certaines de ces dépendances que nous sommes censés changer pour avoir une nouvelle implémentation.
Ce que nous aimerions faire, c'est avoir la possibilité de configurer différentes implémentations de dépendances pour satisfaire cette multitude de clients. Malheureusement, DI ne semble pas être une option car les clients n'acceptent pas les injections de constructeurs ou de poseurs.
Les options:
1) Refactoriser la mise en œuvre du service que les clients utilisent pour qu'il fasse ce dont les clients ont besoin maintenant. Bang, nous avons terminé. Pas flexible. Pas complexe.
2) Refactorisez l'implémentation afin qu'elle délègue son travail à une autre dépendance qu'elle acquiert via une usine. Nous pouvons maintenant contrôler quelle implémentation ils utilisent tous en refactorisant l'usine.
3) Refactorisez l'implémentation afin qu'elle délègue son travail à une autre dépendance qu'elle acquiert via un localisateur de services. Maintenant, nous pouvons contrôler quelle implémentation ils utilisent tous en configurant le localisateur de service qui pourrait simplement être une hashmap
chaîne de caractères vers des objets avec un petit casting en cours.
4) Quelque chose auquel je n'ai même pas encore pensé.
L'objectif:
Minimisez les dommages causés à la conception en faisant glisser l'ancien code client mal conçu vers l'avenir sans ajouter de complexité inutile.
Les clients ne doivent pas connaître ou contrôler l'implémentation de leurs dépendances mais ils insistent pour les construire avec new
. Nous ne pouvons pas contrôler le new
mais nous contrôlons la classe qu'ils construisent.
Ma question:
Qu'est-ce que j'ai omis de considérer?
avez-vous vraiment besoin d'une possibilité de configuration entre différentes implémentations? Dans quel but?
Agilité. Beaucoup d'inconnues. La direction souhaite un potentiel de changement. Ne perdez votre dépendance vis-à-vis du monde extérieur. Test également.
avez-vous besoin d'une mécanique d'exécution ou simplement d'une mécanique de compilation pour basculer entre différentes implémentations? Pourquoi?
La mécanique du temps de compilation est probablement suffisante. Sauf pour les tests.
De quelle granularité avez-vous besoin pour basculer entre les implémentations? Tout à la fois? Par module (contenant chacun un groupe de classes)? Par classe?
Sur les 1001, un seul est exécuté à travers le système à la fois. Changer ce que tous les clients utilisent en même temps est très bien. Le contrôle individuel des dépendances est cependant probablement important.
qui doit contrôler l'interrupteur? Uniquement votre / votre équipe de développeurs? Un administrateur? Chaque client seul? Ou les développeurs de maintenance pour le code du client? Alors, comment la mécanique doit-elle être facile / robuste / infaillible?
Dev pour les tests. Administrateur lorsque les dépendances matérielles externes changent. Il doit être facile à tester et à configurer.
Notre objectif est de montrer que le système peut être refait rapidement et modernisé.
cas d'utilisation réel du commutateur d'implémentation?
Premièrement, certaines données seront fournies par le logiciel jusqu'à ce que la solution matérielle soit prête.
la source
Réponses:
Eh bien, je ne suis pas sûr de bien comprendre les détails techniques et leurs différences exactes de vos solutions prises en charge, mais à mon humble avis, vous devez d'abord savoir de quel type de flexibilité vous avez vraiment besoin.
Les questions que vous devez vous poser sont:
avez-vous vraiment besoin d'une possibilité de configuration entre différentes implémentations? Dans quel but?
avez-vous besoin d'une mécanique d'exécution ou simplement d'une mécanique de compilation pour basculer entre différentes implémentations? Pourquoi?
De quelle granularité avez-vous besoin pour basculer entre les implémentations? Tout à la fois? Par module (contenant chacun un groupe de classes)? Par classe?
qui doit contrôler l'interrupteur? Uniquement votre / votre équipe de développeurs? Un administrateur? Chaque client seul? Ou les développeurs de maintenance pour le code du client? Alors, comment la mécanique doit-elle être facile / robuste / infaillible?
Ayant à l'esprit les réponses à ces questions, choisissez la solution la plus simple à laquelle vous pouvez penser qui vous offre la flexibilité requise. N'implémentez aucune flexibilité dont vous n'êtes pas sûr "au cas où" si cela signifie un effort supplémentaire.
En réponse à vos réponses: si vous avez au moins un cas d'utilisation réel à portée de main, utilisez-le pour valider vos décisions de conception. Utilisez ce cas d'utilisation pour savoir quel type de solution vous convient le mieux. Essayez simplement si une «usine» ou un «localisateur de services» vous fournit ce dont vous avez besoin, ou si vous avez besoin d'autre chose. Si vous pensez que les deux solutions sont également bonnes pour votre cas, jetez un dé.
la source
Juste pour être sûr de bien comprendre. Vous avez un service composé de certaines classes, disons,
C1,...,Cn
et d'un groupe de clients qui appellent directementnew Ci(...)
. Je pense donc que je suis d'accord avec la structure générale de votre solution qui consiste à créer un nouveau service interne avec de nouvelles classesD1,...,Dn
qui sont agréables et modernes et injecter leurs dépendances (permettant de telles dépendances à travers la pile), puis réécrireCi
pour ne rien faire d'autre qu'instancier et delgate àDi
. La question est de savoir comment faire et vous avez proposé une façon de couple2
et3
.Pour donner une suggestion similaire à
2
. Vous pouvez utiliser l'injection de dépendances tout au longDi
et en interne, puis créer une racine de composition normaleR
(en utilisant un framework si vous le jugez approprié) qui est responsable de l'assemblage du graphe d'objet. ShoveR
derrière une usine de statique et que chacun l'Ci
obtenir estDi
par là. Ainsi, par exemple, vous pourriez avoirIl s'agit essentiellement de votre solution 2, mais elle rassemble toutes vos usines en un seul emplacement avec le reste de l'injection de dépendance.
la source
getInstance()
?Commencez par des implémentations simples, sans fantaisie et singulières.
Si vous devez par la suite créer des implémentations supplémentaires, il s'agit de savoir quand la décision d'implémentation se produit.
Si vous avez besoin d'une flexibilité "au moment de l'installation" (chaque installation client utilise une seule implémentation statique), vous n'avez toujours rien à faire de fantaisiste. Vous proposez simplement différentes DLL ou SO (ou quel que soit votre format de bibliothèque). Le client a juste besoin de mettre le bon dans le
lib
dossier ...Si vous avez besoin d'une flexibilité d'exécution, vous aurez juste besoin d'un adaptateur fin et d'un mécanisme de sélection d'implémentation. Que vous utilisiez une usine, un localisateur ou un conteneur IoC est principalement théorique. Les seules différences importantes entre un adaptateur et un localisateur sont A) la dénomination et B) si l'objet renvoyé est un singleton ou une instance dédiée. Et la grande différence entre un conteneur IoC et une usine / localisateur est qui appelle qui . (Souvent une question de préférence personnelle.)
la source