J'ai réfléchi à la manière d'équilibrer la conception testable à l'aide de l'injection de dépendances avec la fourniture d'une API publique fixe simple. Mon dilemme est: les gens voudraient faire quelque chose comme ça var server = new Server(){ ... }
et ne devraient pas avoir à se soucier de créer les nombreuses dépendances et le graphique des dépendances que Server(,,,,,,)
peut avoir un. Pendant le développement, je ne m'inquiète pas trop, car j'utilise un framework IoC / DI pour gérer tout cela (je n'utilise pas les aspects de gestion du cycle de vie d'un conteneur, ce qui compliquerait les choses).
Désormais, il est peu probable que les dépendances soient réimplémentées. La composanteisation dans ce cas est presque purement pour la testabilité (et une conception décente!) Plutôt que de créer des coutures pour l'extension, etc. Les gens voudront 99,999% du temps souhaiter utiliser une configuration par défaut. Donc. Je pourrais coder en dur les dépendances. Je ne veux pas faire ça, nous perdons nos tests! Je pourrais fournir un constructeur par défaut avec des dépendances codées en dur et un qui accepte les dépendances. C'est ... désordonné et susceptible de prêter à confusion, mais viable. Je pourrais rendre le constructeur de réception de dépendance interne et faire de mes tests unitaires un assemblage ami (en supposant C #), qui nettoie l'API publique mais laisse un piège caché méchant qui se cache pour la maintenance. Avoir deux constructeurs qui sont implicitement connectés plutôt qu'explicitement serait une mauvaise conception en général dans mon livre.
En ce moment, c'est à peu près le moins de mal auquel je puisse penser. Des avis? Sagesse?
HttpListener
constructeur change, laServer
classe doit également changer. Ils sont couplés. Une meilleure solution est ce que dit @Winston Ewert ci-dessous, qui est de fournir une classe par défaut avec des dépendances pré-spécifiées, en dehors de l'API de la bibliothèque. Ensuite, le programmeur n'est pas obligé de configurer toutes les dépendances puisque vous prenez la décision pour lui, mais il a toujours la possibilité de les modifier plus tard. C'est un gros problème lorsque vous avez des dépendances tierces.En Java, dans de tels cas, il est habituel d'avoir une configuration "par défaut" construite par une usine, maintenue indépendante du serveur qui se comporte "correctement". Ainsi, votre utilisateur écrit:
tandis que le
Server
constructeur de accepte toujours toutes ses dépendances et peut être utilisé pour une personnalisation approfondie.la source
Que diriez-vous de fournir une sous-classe qui fournit les "API publiques"
L'utilisateur peut
new StandardServer()
et être en route. Ils peuvent également utiliser la classe de base Server s'ils souhaitent plus de contrôle sur le fonctionnement du serveur. C'est plus ou moins l'approche que j'utilise. (Je n'utilise pas de framework car je n'ai pas encore vu le point.)Cela expose toujours l'api interne, mais je pense que vous devriez. Vous avez divisé vos objets en différents composants utiles qui devraient fonctionner indépendamment. Il est impossible de savoir quand un tiers peut vouloir utiliser l'un des composants de manière isolée.
la source
Cela ne me semble pas être un "piège méchant caché". Le constructeur public devrait simplement appeler celui interne avec les dépendances "par défaut". Tant qu'il est clair que le constructeur public ne doit pas être modifié, tout devrait bien se passer.
la source
Je suis totalement d'accord avec votre opinion. Nous ne voulons pas polluer la limite d'utilisation des composants uniquement à des fins de tests unitaires. C'est tout le problème de la solution de test basée sur DI.
Je suggère d'utiliser le modèle InjectableFactory / InjectableInstance . InjectableInstance est une simple classe d'utilitaires réutilisable qui est titulaire d'une valeur mutable . L'interface du composant contient une référence à ce détenteur de valeur qui s'est initialisé avec l'implémentation par défaut. L'interface fournira une méthode singleton get () qui déléguera au détenteur de la valeur. Le client composant appellera la méthode get () au lieu de new pour obtenir une instance d'implémentation afin d'éviter une dépendance directe. Pendant le temps de test, nous pouvons éventuellement remplacer l'implémentation par défaut par une maquette. Après, je vais utiliser un exemple TimeProviderqui est une abstraction de Date (), ce qui permet aux tests unitaires d'injecter et de simuler pour simuler différents moments. Désolé, je vais utiliser java ici car je suis plus familier avec java. C # devrait être similaire.
L'InjectableInstance est assez facile à implémenter, j'ai une implémentation de référence en java. Pour plus de détails, veuillez vous référer à mon article de blog Indépendance de dépendance avec Injectable Factory
la source