30 votes

comment mettre en œuvre l'IOC sans un service statique global (solution sans localisateur de service) ?

Nous voulons utiliser Unity pour le CIO. Tout ce que j'ai vu, c'est l'implémentation d'un service statique global (appelons-le IOCService) qui contient une référence au conteneur Unity, qui enregistre toutes les combinaisons interface/classe et chaque classe demande à cet objet : donnez-moi une implémentation pour Ithis ou IThat.

On me répond souvent que ce modèle n'est pas bon parce qu'il entraîne une dépendance de TOUTES les classes vers l'IOCService (et non vers le conteneur Unity car il n'est connu qu'à l'intérieur de l'IOCService).

Mais ce que je ne vois pas souvent, c'est : quelle est la voie alternative ?

Michel

EDIT : j'ai découvert que le service statique global s'appelle le localisateur de services, j'ai ajouté cela au titre.

11voto

Jeff Sternal Points 30147

L'alternative est d'avoir une seule instance de votre conteneur au niveau le plus élevé de l'application uniquement puis utilisez ce conteneur pour résoudre chaque instance d'objet que vous devez créer dans cette couche.

Par exemple, la méthode principale de la plupart des exécutables ressemble à ceci (sans la gestion des exceptions) :

private static void main(string[] args) {

     Container container = new Container();

     // Configure the container - by hand or via file

     IProgramLogic logic = container.Resolve<IProgramLogic>();

     logic.Run();
}

Votre programme (représenté ici par l'icône IProgramLogic ) n'a pas besoin de savoir quoi que ce soit sur votre conteneur, car container.Resolve créera toutes ses dépendances - et les dépendances de ses dépendances, jusqu'aux classes feuilles qui n'ont pas de dépendances propres.


ASP.NET est un cas plus difficile, car les formulaires web ne supportent pas l'injection de constructeur. J'utilise typiquement Model-View-Presenter dans mes applications de formulaires web, donc mon module Page Les classes n'ont en réalité qu'une seule dépendance à l'égard de leur présentateur. Je ne les teste pas à l'unité (tout ce qui est intéressant et testable se trouve dans mes présentateurs, que je faire ), et je ne remplace jamais les présentateurs. Je ne me bats donc pas contre le framework - je me contente d'exposer une propriété de conteneur sur mon HttpApplication (dans global.asax.cs) et de l'utiliser directement à partir de mon fichier Page des fichiers :

protected void Page_Load(object sender, EventArgs args) {
    ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>();
    presenter.Load();
}

C'est le localisateur de service bien sûr - bien que le Page sont les seuls éléments couplés au localisateur : votre présentateur et toutes ses dépendances sont encore totalement découplés de l'implémentation de votre conteneur IoC.

Si vous avez beaucoup de dépendances dans votre fichier Page (c'est-à-dire si vous n'utilisez pas Model-View-Presenter), ou s'il est important pour vous de découpler vos fichiers Page des classes de votre Global vous devriez essayer de trouver un framework qui s'intègre dans le pipeline de requêtes des formulaires web et utiliser l'injection de propriétés (comme suggéré par Nicholas dans les commentaires ci-dessous) - ou écrire votre propre classe d'application IHttpModule et effectuer vous-même l'injection de la propriété.

7voto

Krzysztof Kozmic Points 19262

+1 pour connaître que le Service Locator est une mauvaise chose .

Le problème, c'est que Unity n'est pas très sophistiqué et que je ne sais pas s'il est facile ou difficile d'utiliser IoC de la bonne façon.

J'ai écrit récemment quelques articles de blog qui pourraient vous être utiles.

5voto

Garo Yeriazarian Points 2189

Au lieu d'utiliser le conteneur de manière explicite, utilisez-le de manière implicite en tirant parti de l'injection de constructeurs et de propriétés. Créez une classe centrale (ou un ensemble de classes centrales) qui dépendent de tous les éléments principaux de votre application.

La plupart des conteneurs vous permettront de mettre ISomething[] dans votre constructeur et il injectera toutes les instances de ISomething dans votre classe.

De cette façon, lorsque vous démarrez votre application :

  1. Instanciez votre conteneur
  2. Enregistrez tous vos goodies
  3. Résolvez les classes de base (cela va attirer toutes les autres dépendances dont vous avez besoin).
  4. Exécuter la partie "principale" de l'application

Maintenant, selon le type d'application que vous écrivez, il existe différentes stratégies pour éviter de marquer le conteneur IoC comme "statique".

Pour les applications Web ASP.NET, vous finirez probablement par stocker le conteneur dans l'état d'application. Pour les applications ASP.NET MVC, vous devez modifier le Controller Factory.

Pour les applications de bureau, les choses se compliquent. Caliburn utilise une solution intéressante à ce problème en utilisant le IRésultat (Ceci est pour les applications WPF mais pourrait être adapté pour Windows Forms aussi.

4voto

Igor Zevaka Points 32586

En théorie, pour ne pas avoir à se soucier de disposer d'une instance IoC statique, il suffit de suivre la règle suivante Règle du Fight Club - c'est-à-dire ne pas parler du fight club - c'est-à-dire ne pas parler du conteneur IoC.

Cela signifie que vos composants doivent ignorer en grande partie le conteneur IoC. Il ne doit être utilisé qu'au niveau le plus élevé lors de l'enregistrement des composants. Si une classe doit résoudre quelque chose, il faut vraiment l'injecter comme une dépendance.

Le cas trivial est assez facile. Si PaymentService dépend de IAccount ce dernier doit être injecté par IoC :

interface IAccount {
  Deposit(int amount);
}

interface CreditCardAccount : IAccount {
  void Deposit(int amount) {/*implementation*/}
  int CheckBalance() {/*implementation*/}
}

class PaymentService {

  IAccount account;

  public PaymentService (IAccount account) {
    this.account = account;
  }

  public void ProcessPayment() {
    account.Deposit(5);
  }
}
//Registration looks something like this
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();

Le cas pas si trivial est celui où vous voulez injecter des enregistrements multiples. Cela s'applique tout particulièrement lorsque vous effectuez une sorte de conversion sur la configuration et que vous créez un objet à partir d'un nom.

Pour notre exemple de paiement, disons que vous voulez énumérer tous les comptes et vérifier leur solde :

class PaymentService {

  IEnumerable<IAccount> accounts;

  public PaymentService (IEnumerable<IAccount> accounts) {
    this.accounts = accounts;
  }

  public void ProcessPayment() {
    foreach(var account in accounts) {
      account.Chackbalance();
    }
  }
}

Unity a la possibilité d'enregistrer plusieurs mappages d'interfaces vers des classes (il faut penser qu'ils doivent avoir des noms différents). Cependant, il ne les injecte pas automatiquement dans les classes qui prennent des collections de ces interfaces enregistrées. Ainsi, l'exemple ci-dessus déclenchera une exception d'échec de résolution au moment de l'exécution.

Si vous ne vous souciez pas que ces objets vivent éternellement, vous pouvez enregistrer PaymentService d'une manière plus statique :

container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>()));

Le code ci-dessus enregistrera PaymentService et utilisera une collection de IAccount qui est résolu au moment de l'enregistrement.

Ou bien vous pouvez passer une instance du conteneur lui-même en tant que dépendance et laisser PaymentService effectuer la résolution des comptes. Cela ne suit pas tout à fait la règle du Fight Club, mais c'est légèrement moins odorant que le localisateur de services statiques.

class PaymentService {

  IEnumerable<IAccount> accounts;

  public PaymentService (IUnityContainer container) {
    this.accounts = container.ResolveAll<IAccount>();
  }

  public void ProcessPayment() {
    foreach(var account in accounts) {
      account.Chackbalance();
    }
  }
}
//Registration is pretty clean in this case
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
container.RegisterInstance<IUnityContainer>(container);

2voto

Jay Points 27907

Si votre préoccupation est d'avoir une dépendance à Unity dans toute votre application, vous pouvez combiner le localisateur de services avec une façade pour cacher l'implémentation de l'IOC. De cette manière, vous ne créez pas de dépendance vis-à-vis de Unity dans votre application, mais seulement vis-à-vis de l'utilisation de la fonction quelque chose qui peut résoudre les types pour vous.

Par exemple :

public interface IContainer
{
    void Register<TAbstraction,TImplementation>();
    void RegisterThis<T>(T instance);
    T Get<T>();
}

public static class Container
{
    static readonly IContainer container;

    public static InitializeWith(IContainer containerImplementation)
    {
        container = containerImplementation;
    }

    public static void Register<TAbstraction, TImplementation>()
    {
        container.Register<TAbstraction, TImplementation>();
    }

    public static void RegisterThis<T>(T instance)
    {
        container.RegisterThis<T>(instance);
    }

    public static T Get<T>()
    {
        return container.Get<T>();
    }
}

Maintenant, tout ce dont vous avez besoin est un IContainer pour le conteneur IOC de votre choix.

public class UnityContainerImplementation : IContainer
{
    IUnityContainer container;

    public UnityContainerImplementation(IUnityContainer container)
    {
        this.container = container;
    }

    public void Register<TAbstraction, TImplementation>()
    {
        container.Register<TAbstraction, TImplementation>();
    }

    public void RegisterThis<T>(T instance)
    {
        container.RegisterInstance<T>(instance);
    }

    public T Get<T>()
    {
        return container.Resolve<T>();
    }
}

Vous avez maintenant un localisateur de services qui est une façade pour les services IOC, et vous pouvez configurer votre localisateur de services pour utiliser Unity ou tout autre conteneur IOC. Le reste de l'application ne dépend pas de l'implémentation de l'IOC.

Pour configurer votre localisateur de services :

IUnityContainer unityContainer = new UnityContainer();
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer);
Container.InitializeWith(containerImpl);

Pour le test, vous pouvez créer un stub de IContainer qui renvoie ce que vous voulez, et initialiser Container avec ça.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X