web-dev-qa-db-fra.com

Résolveur de dépendance simple

Comment créer un résolveur de dépendances simple, sans utiliser de bibliothèque intégrée ou comme Autofac, Ninject, etc.

C'était ma question d'entrevue.

J'ai écrit ce code simple et ils ont dit que ça n'avait pas l'air bien. C'est comme une idée codée très dur.

public interface IRepository { }
interface IDataProvider
{
    List<string> GetData();
}
public class SQLDataProvider : IDataProvider
{
    private readonly IRepository _repository { get; set; }
    public SQLDataProvider(IRepository repository)
    {
        _repository = repository;
    }
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
public class MockDataProvider : IDataProvider
{
    public List<string> GetData()
    {
        return new List<string> { "" };
    }
}
class Program
{
 static void Main(string[] args)
 {
    string targetClass = "SQLDataProvider";
    //Here i need to supply IRepository instance too 
   IDataProvider dataProvider = 
   (IDataProvider)Activator.CreateInstance(typeof(IDataProvider), targetClass);

  }
}

Quel meilleur code je fais et fournit une autre instance d'objet pour le paramètre constructeur?

30
Billa

Vous pouvez écrire un conteneur en seulement quelques lignes de code. À la base, ce serait généralement un dictionnaire avec System.Type comme clé et la valeur serait un objet qui vous permet de créer de nouvelles instances de ce type. Lorsque vous écrivez une implémentation simple System.Func<object> ferait. Voici une implémentation simple qui contient plusieurs méthodes Register, à la fois une méthode générique et non générique GetInstance et permet le câblage automatique:

public class Container 
{
    Dictionary<Type, Func<object>> registrations = new Dictionary<Type, Func<object>>();

    public void Register<TService, TImpl>() where TImpl : TService {
        this.registrations.Add(typeof(TService), () => this.GetInstance(typeof(TImpl)));
    }

    public void Register<TService>(Func<TService> instanceCreator) {
        this.registrations.Add(typeof(TService), () => instanceCreator());
    }

    public void RegisterSingleton<TService>(TService instance) {
        this.registrations.Add(typeof(TService), () => instance);
    }

    public void RegisterSingleton<TService>(Func<TService> instanceCreator) {
        var lazy = new Lazy<TService>(instanceCreator);
        this.Register<TService>(() => lazy.Value);
    }

    public object GetInstance(Type serviceType) {
        Func<object> creator;
        if (this.registrations.TryGetValue(serviceType, out creator)) return creator();
        else if (!serviceType.IsAbstract) return this.CreateInstance(serviceType);
        else throw new InvalidOperationException("No registration for " + serviceType);
    }

    private object CreateInstance(Type implementationType) {
        var ctor = implementationType.GetConstructors().Single();
        var parameterTypes = ctor.GetParameters().Select(p => p.ParameterType);
        var dependencies = parameterTypes.Select(t => this.GetInstance(t)).ToArray();
        return Activator.CreateInstance(implementationType, dependencies);
    }
}

Vous pouvez l'utiliser comme suit:

var container = new Container();

container.RegisterSingleton<ILogger>(new FileLogger("c:\\logs\\log.txt"));

// SqlUserRepository depends on ILogger
container.Register<IUserRepository, SqlUserRepository>();

// HomeController depends on IUserRepository
// Concrete instances don't need to be resolved
container.GetInstance(typeof(HomeController));

[~ # ~] avertissement [~ # ~] :

Veuillez noter que vous ne devez jamais utiliser une telle implémentation. Il manque de nombreuses fonctionnalités importantes que les bibliothèques DI vous offrent, mais ne donne aucun avantage sur l'utilisation de Pure DI (c'est-à-dire des graphiques d'objets à câblage manuel). Vous perdez le support à la compilation, sans rien récupérer.

Lorsque votre application est petite, vous devez commencer avec Pure DI et une fois que votre application et votre configuration DI se développent au point que vous maintenir Composition Root devient lourd, vous pouvez envisager de passer à l'une des bibliothèques DI établies .

Voici quelques-unes des fonctionnalités qui manquent à cette implémentation naïve par rapport aux bibliothèques établies:

  • Enregistrement par lots (enregistrement d'un ensemble de types avec une seule ligne)
  • Application de décorateurs ou d'intercepteurs pour une gamme de types
  • Mappage d'abstractions génériques ouvertes à des implémentations génériques ouvertes
  • Intégration avec des plates-formes d'applications courantes (telles que ASP.NET MVC, API Web, etc.)
  • Enregistrement de types avec des styles de vie personnalisés.
  • Rapport d'erreurs décent (au lieu de lever des exceptions de débordement de pile par exemple)
  • Outils pour vérifier l'exactitude de la configuration (pour compenser la perte de prise en charge au moment de la compilation) et diagnostiquer les erreurs de configuration courantes.
  • Bonne performance.

Ces fonctionnalités vous permettent de maintenir votre configuration DI maintenable.

31
Steven

Cela fait déjà quelques années, mais Ayende a écrit un article à ce sujet:
Création d'un conteneur IoC en 15 lignes de code

Mais ce n'est que la mise en œuvre la plus simple possible.
Ayende lui-même a déclaré dans son article suivant que les conteneurs IoC existants peuvent faire bien plus que simplement renvoyer des instances de classe - et c'est là que cela se complique.
Comme "Faites-moi confiance - je suis un docteur" l'a déjà dit dans son commentaire: implémenter un complete conteneur IoC est tout sauf trivial.

4
Christian Specht