2008-09-24 6 views
6

This question about unit testing best practices mentionne la conception de classes pour l'injection de dépendances. Cela m'a fait réfléchir à ce que cela signifiait exactement. Après avoir commencé à travailler avec des conteneurs d'inversion de contrôle, j'ai quelques idées sur la question, alors laissez-moi les jeter contre le mur et voir ce qui colle. Comme je le vois, il existe trois types de dépendances de base qu'un objet peut avoir.Lignes directrices pour la conception de classes pour l'injection de dépendances

  1. Objet de dépendance - Un objet réel qui sera utilisé par la classe en question. Par exemple LogInVerifier dans un LogInFormController. Ceux-ci devraient être injectés à travers le constructeur. Si la classe est de niveau suffisamment élevé, cela nécessite plus de 4 de ces objets dans le constructeur de la décomposer ou tout au moins en utilisant un modèle d'usine. Vous devez également envisager de fournir la dépendance avec une interface et un codage par rapport à l'interface.
  2. Un paramètre simple - Par exemple un seuil ou un délai d'attente. Ceux-ci devraient généralement avoir une valeur par défaut et être définis via un constructeur de modèle d'usine. Vous pouvez également fournir des surcharges de constructeur qui les définissent. Cependant, dans la plupart des cas, vous ne devriez probablement pas forcer le client à le configurer explicitement.
  3. Un objet de message - Un objet qui est transféré d'une classe à une autre que la classe de réception utilise vraisemblablement pour la logique métier. Un exemple serait un objet Utilisateur pour une classe LogInCompleRouter. Ici, je trouve qu'il est souvent préférable que le message ne soit pas spécifié dans le constructeur car vous devrez soit enregistrer l'instance User avec le conteneur IoC (le rendre global), soit ne pas instancier le LogInCompleteRouter jusqu'à ce que vous ayez une instance de User (pour lequel vous ne pourriez pas utiliser DI ou au moins aurait besoin d'une dépendance explicite sur le conteneur). Dans ce cas, il est préférable de ne transmettre l'objet message que lorsque vous en avez besoin pour l'appel de la méthode (par exemple, LoginInCompleteRouter.Route (Utilisateur u);).

Aussi, je dois dire que tout ne devrait être DI'ed, si vous avez un peu simple de fonctionnalité qui était pratique pour factoriser à une classe à jeter, il est probablement autorisé à instancier sur place. Évidemment, c'est un appel de jugement; si je l'ai trouvé opportun d'écrire une classe telle que

class PasswordEqualsVerifier { 
    public bool Check(string input, string actual) { return input===actual;} 
} 

Je ne serais probablement pas la peine dépendance injection et aurait juste un objet instancier directement dans un bloc à l'aide. Le corollaire étant que si cela vaut la peine d'écrire des tests unitaires, alors cela vaut probablement la peine d'injecter.

Alors, qu'en pensez-vous? Toutes les directives supplémentaires ou opinions contrastées sont les bienvenues.

Répondre

1

L'important est d'essayer de coder les interfaces et d'avoir vos classes acceptent des instances de ces interfaces plutôt que de créer les instances elles-mêmes. Vous pouvez évidemment devenir fou avec ceci, mais c'est une bonne pratique générale indépendamment des tests unitaires ou DI.

Par exemple, si vous avez un objet d'accès aux données, vous pourriez être tenté d'écrire une base pour tous OTI comme celui-ci:

public class BaseDAO 
{ 
    public BaseDAO(String connectionURL, 
        String driverName, 
        String username, String password) 
    { 
     // use them to create a connection via JDBC, e.g. 
    } 

    protected Connection getConnection() { return connection; } 
} 

Cependant, il serait préférable de le supprimer de la classe en faveur d'une interface

public interface DatabaseConnection 
{ 
    Connection getConnection(); 
} 

public class BaseDAO 
{ 
    public BaseDAO(DatabaseConnection dbConnection) 
    { 
     this.dbConnection = dbConnection; 
    } 

    protected Connection getConnection() { return dbConnection.getConnection(); } 
} 

maintenant, vous pouvez fournir imlementations multilple de DatabaseConnection. Même en ignorant les tests unitaires, si nous supposons que nous utilisons JDBC, il existe deux façons d'obtenir un Connection: un pool de connexions à partir du conteneur, ou directement en utilisant le pilote. Maintenant, votre code DAO n'est couplé à aucune stratégie.

Pour tester, vous pouvez créer un MockDatabaseConnection qui se connecte à une implémentation JDBC intégrée avec des données prédéfinies pour tester votre code.