Je n'ai rien contre les DI containers en tant que tels. Je pratique l'injection de dépendances aussi souvent que nécessaire mais j'ai un problème avec les librairies et frameworks en tout genre qui me donnent plus de complications à gérer que d'écrire le code correspondant aux 10% de fonctionnalités dont j'ai réellement besoin.

C'est pour cela que je lis avec grand plaisir des phrases telles que "Dependency Injection doesn’t require a framework; it just requires that you invert your dependencies and then construct and pass your arguments to deeper layers" venant de quelqu'un dont la crédibilité n'est plus à démontrer.

L'injection de dépendances, ce n'est jamais qu'un cas particulier de l'inversion de dépendances, c'est à dire d'une structuration logicielle où les détails d'implémentation ne dépendent pas l'un de l'autre mais dépendent tous d'abstractions.
La spécificité de l'injection de dépendances, c'est, étant donnés un contexte et une abstraction, d'être capable de fournir automatiquement le détail d'implémentation de l'abstraction pour le contexte considéré. Bref, si on est capable d'implémenter l'appel suivant (écrit dans un pseudo langage objet), on est sur la bonne voie :

instance = context.GetDetailsFor(abstraction)

Une abstraction, c'est typiquement ce que, dans les langages orientés objets les plus courants, on appelle une "interface", Mais un contexte ? J'ai un théorème pour répondre à ça.

Théorème de oaz sur l'injection de dépendance

Le contexte d'une injection de dépendance peut se limiter à un choix de package

Démonstration

Une abstraction étant fixée, une injection de dépendance est le choix d'une classe implémentant cette abstraction et des arguments de construction d'une instance de cette classe.
Les arguments de constructions peuvent être rendus optionnels : il suffit pour cela d'utiliser une classe dérivée ad hoc où les arguments en question sont fixés.
Reste à choisir la classe à instancier.
Si une injection de dépendance nécessitait plus qu'un choix de package, cela signifierait que, en plus du package choisi, il faut d'autres informations qui permettent de choisir telle ou telle classe du package implémentant l'abstraction considérée. Cela implique que, parmi les classes du package choisi, au moins deux d'entre elles implémentent la même abstraction injectable et et ces deux ne vont donc pas être utilisées simultanément.
L'existence de ces deux classes dans le même package est une violation du Common Reuse Principle qui dit que "The classes in a package a reused together. If you reuse one of the classes in a package, you reuse them all"


Ce résultat théorique n'est pas toujours pratique à mettre en oeuvre (du fait de la nécessité d'une classe dérivée dans certains cas) mais pour un grand nombre de cas d'injection de dépendances, il suffit amplement.

Et, ce qui n'est pas pour me déplaire, dans un langage pas trop mal fichu, il me donne l'occasion d'écrire un "framework" basique d'injection de dépendances en 5 ou 6 lignes de code :

public static class MyDIContainer
{
  public static ABSTRACTION GetDetailsFor<ABSTRACTION>(this Assembly a)
  {
      var candidates = a.GetTypes().Where(t => typeof(ABSTRACTION).IsAssignableFrom(t));
      if(candidates.Count() != 1)
        throw new ApplicationException(string.Format("Cannot find unique implementation of {0} in {1}", typeof(ABSTRACTION), a));
      return (ABSTRACTION) Activator.CreateInstance(candidates.First());
  }
}

Ce "framework", qui se contente de retrouver dans un package donné l'unique classe qui implémente une interface donnée, s'utilise de la façon suivante :

var context = Assembly.LoadFile("DependenciesToInject.dll");
var instance = context.GetDetailsFor<IAmAnAbstraction>();

Une manière simple d'étendre ce "framework" pour prendre la main sur la phase de construction des instances, c'est de rajouter une interface "IProvideCustomDetails" et de l'implémenter dans les packages qui ont besoin de cette spécificité.

public interface IProvideCustomDetails
{
  ABSTRACTION GetDetailsFor<ABSTRACTION>() where ABSTRACTION : class;
}

public static class MyDIContainer
{
  public static ABSTRACTION GetDetailsFor<ABSTRACTION>(this Assembly a) where ABSTRACTION : class
  {
    return a.GetCustomDetailsFor<ABSTRACTION>() ?? a.GetUniqueImplementationFor<ABSTRACTION>();
  }
	
  public static ABSTRACTION GetUniqueImplementationFor<ABSTRACTION>(this Assembly a) where ABSTRACTION : class
  {
    var candidates = a.GetTypes().Where(t => typeof(ABSTRACTION).IsAssignableFrom(t));
    if(candidates.Count() != 1)
      throw new ApplicationException(string.Format("Cannot find unique implementation of {0} in {1}", typeof(ABSTRACTION), a));
    return (ABSTRACTION) Activator.CreateInstance(candidates.First());
  }
	
  public static ABSTRACTION GetCustomDetailsFor<ABSTRACTION>(this Assembly a) where ABSTRACTION : class
  {
    var provider = a.GetUniqueImplementationFor<IProvideCustomDetails>();
    return ( provider == null ) ? null : provider.GetDetailsFor<ABSTRACTION>();
  }
}

Bien évidemment, ce genre de code ne va pas rendre l'intégralité des services que rendrait un conteneur de DI "prêt à l'emploi" mais, en ce qui me concerne, je n'ai pas besoin de beaucoup plus que ça (typiquement j'aurais tendance à rajouter un passage de données de configuration à l'appel GetDetailsFor). Je n'ai donc aucune incitation pour utiliser des choses bien plus compliquées et qui nécessitent un suivi ne serait-ce que pour intégrer les mises à jour qui ne manquent pas de fleurir.

Peut être que ce choix n'est finalement qu'une question de contexte de développement. Un développeur qui vogue de projet en projet aura tout intérêt à débarquer avec une panoplie de connaissances directement utilisables. La maîtrise d'un ou plusieurs DI container du marché en fait partie. Cela peut permettre de gagner du temps lors d'un démarrage de projet.
Mais un développeur qui passe des années à faire évoluer les mêmes produits, a-t-il le même intérêt ?
Gardons cela pour un autre débat...