2010-11-03 2 views
2

Nous avons un objet assez commun dans notre application. Dans ce cas, nous l'appellerons une balle. Les balles fonctionnent bien, mais dans certaines configurations, elles agissent différemment. Il est actuellement mis en place comme ceci:Objet couramment utilisé avec dépendance de la configuration

class Ball 
{ 
    private static readonly bool BallsCanExplode; 
    static Ball() 
    { 
     bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"], 
      out BallsCanExplode); 
    } 
    public Ball(){} 
} 

Cela fonctionne très bien dans la pratique. Si la configuration est que les balles peuvent exploser, elles explosent, et sinon, non. Le problème est qu'il est complètement non testable. Je n'ai pas été en mesure de trouver un bon moyen de le garder testable, et encore facile à instancier.

La solution la plus simple est juste découpler la balle et la configuration:

class Ball 
{ 
    private readonly bool CanExplode; 
    public Ball(bool canExplode); 
} 

Le problème est que ce qui était autrefois une dépendance isolée dans la classe Ball est maintenant étendue à chaque classe qui fait Ballon. Si cela devient une dépendance injectée, alors la connaissance des boules qui explosent doit être injectée partout.

Le même problème existe avec une BallFactory. Alors que chaque classe peut simplement aller new Ball(), il doit maintenant connaître une BallFactory qui doit être injectée partout. L'autre option est d'utiliser le service de localisation qui est déjà cuit à l'application:

class Ball 
{ 
    private readonly bool CanExplode; 
    public Ball() 
    { 
     CanExplode = ServiceLocator.Get<IConfiguration>().Get("ballsCanExplode"); 
    } 
} 

Cela permet de maintenir encore la dépendance de configuration dans le ballon, mais permet une configuration de test à injecter dans Balls sont utilisés tant. cependant, que cela semble être trop difficile de localiser le service à chaque appel new Ball().

Quelle serait la meilleure façon de garder ce testable, ainsi que facile à instancier?

Remarque: Il existe à la fois un cadre d'injection de dépendances et un localisateur de service dans l'application, qui sont tous deux fréquemment utilisés.

+0

Est-ce que ces D.I. bibliothèques comme l'aide de Ninject? – xandy

+0

Vous semblez connaître les options d'injection de dépendance, mais vous ne voulez pas les suivre. Par conséquent, votre meilleure option est d'ajouter un constructeur pour tester: public Ball (bool CanExplode) –

Répondre

5

Les classes qui instancient des billes doivent recevoir une BallFactory en tant que dépendance. Le BallFactory peut être configuré en conséquence au démarrage de l'application, qu'il s'agisse de produire des billes explosives ou des billes non explosives.

Ne pas avoir le BallFactory lire le fichier de configuration de l'application pour déterminer les types de billes à produire. Cela devrait être injecté dans le BallFactory.

Les localisateurs de service sont un anti-pattern. Ne les utilisez pas.

+0

Les localisateurs de service peuvent être un moyen propre d'accomplir ceci si vous n'avez pas d'autre méthode d'injection de dépendance. Dans de nombreux cas, une fabrique peut être surdimensionnée, bien qu'elle puisse convenir dans ce cas particulier (où vous devez créer plusieurs instances de la même classe et résoudre un paramètre dans chaque cas). –

+0

Le localisateur de service est déjà présent dans l'application, donc c'est déjà là que ce soit un anti-pattern ou pas. Certaines choses l'utilisent, certaines choses utilisent l'injection de dépendance. – Snea

+2

@Snea: Eh bien, je pense que vous devriez arrêter d'ajouter plus de dépendances sur le localisateur de service. – jason

0

Je vote pour le chemin du service de configuration. La surcharge ne doit pas être très élevée pour une implémentation de localisateur de service typique et vous pouvez mettre en cache le service de configuration si vous en avez besoin plus tard. Mieux encore, utilisez un framework d'injection de dépendances et vous n'aurez pas besoin de localiser explicitement le service.

0

Que diriez-vous quelque chose comme ceci:

internal interface IBallConfigurer 
{ 
    bool CanExplode { get; } 
} 

internal class BallConfigurer : IBallConfigurer 
{ 
    public bool CanExplode 
    { 
     get 
     { 
      bool BallsCanExplode; 
      bool.TryParse(ConfigurationManager.AppSettings["ballsCanExplode"], 
     out BallsCanExplode); 
      return BallsCanExplode; 

     } 
    } 
} 

public class Ball 
{ 
    private bool canExplode; 

    public Ball() 
     :this(new BallConfigurer()) 
    { 

    } 

    internal Ball(IBallConfigurer ballConfigurer) 
    { 
     this.canExplode = ballConfigurer.CanExplode; 
    } 
} 

De cette façon, vous pouvez faire les entrailles de la classe balle visible à votre ensemble de test unitaire et injecter un ballconfigurer personnalisé.

+0

C'est essentiellement la première chose que j'ai essayée, mais avoir un BallConfigurer cuit semblait aller à l'encontre du but de découpler la boule de la classe AppSettings. – Snea

+0

Eh bien, vous avez dit que vous voulez que la balle soit facile à instancier, mais vous voulez qu'elle soit testable. Cela vous donne tout ça. Vous n'avez jamais dit que la boule était trop étroitement liée à la classe AppSetting, vous vouliez juste qu'elle soit suffisamment découplée pour les tests unitaires. Cela vous donne ça. – BFree

2

J'utiliser quelque chose comme votre ServiceLocator pour définir un, alors peut-être statique DefaultBallsCanExplode un constructeur surchargé qui peut prendre un bool ballsCanExplode en option.

Restez simple!

2

Si je comprends bien, toutes les balles de votre application agissent toujours de la même manière. Soit ils explosent ou ils ne le font pas, déterminé par un commutateur de configuration. Ce que vous pouvez faire est de le configurer dans votre cadre DI. Selon le cadre du câblage dans la racine de l'application pourrait ressembler à ceci ::

bool ballsCanExplode = 
    bool.Parse(ConfigurationManager.AppSettings["ballsCanExplode"]); 

container.Register<Ball>(() => new Ball(ballsCanExplode)); 

Lorsque vous faites comme vous êtes déjà utilisé pour ce faire, vous pouvez utiliser le modèle de service de localisation pour aller chercher une nouvelle instance d'une balle:

ServiceLocator.Get<Ball>(); 

Mais mieux serait de laisser le cadre DI injectent Ball dépendances dans le constructeur d'un autre type (beaucoup plus facile pour les tests).

+1

+1 La configuration fait partie de l'initialisation d'une application et non de son exécution. –

Questions connexes