Quelqu'un peut-il expliquer Microsoft Unity?

157

J'ai lu les articles sur MSDN sur Unity (Dependency Injection, Inversion of Control), mais je pense que j'en ai besoin d'expliquer en termes simples (ou des exemples simples). Je suis familier avec le modèle MVPC (nous l'utilisons ici), mais je ne peux pas encore vraiment comprendre ce truc Unity, et je pense que c'est la prochaine étape dans la conception de notre application.

Ryan Abbott
la source
12
J'adore le fait que cela porte le même nom que "Unity", alors quand je cherche des trucs Unity Game Engine, je vois cette vieille technologie, soupire. Tous les bons noms de groupes sont pris, je suppose.
Tom Schulz
2
@ tom-schulz Ancienne technologie? nuget.org/packages/Unity - dernière mise à jour il y a 5 jours.
Roger Willcocks

Réponses:

174

Unity n'est qu'un "conteneur" IoC. Google StructureMap et essayez-le à la place. Un peu plus facile à grok, je pense, quand les trucs IoC sont nouveaux pour vous.

Fondamentalement, si vous comprenez l'IoC, vous comprenez que ce que vous faites est d'inverser le contrôle lorsqu'un objet est créé.

Sans IoC:

public class MyClass
{
   IMyService _myService; 

   public MyClass()
   {
      _myService = new SomeConcreteService();    
   }
}

Avec conteneur IoC:

public class MyClass
{
   IMyService _myService; 

   public MyClass(IMyService myService)
   {
      _myService = myService;    
   }
}

Sans IoC, votre classe qui repose sur IMyService doit renouveler une version concrète du service à utiliser. Et c'est mauvais pour plusieurs raisons (vous avez couplé votre classe à une version concrète spécifique de IMyService, vous ne pouvez pas le tester facilement, vous ne pouvez pas le changer facilement, etc.)

Avec un conteneur IoC, vous «configurez» le conteneur pour résoudre ces dépendances pour vous. Donc, avec un schéma d'injection basé sur un constructeur, vous passez simplement l'interface à la dépendance IMyService dans le constructeur. Lorsque vous créez la MyClass avec votre conteneur, votre conteneur résoudra la dépendance IMyService pour vous.

À l'aide de StructureMap, la configuration du conteneur ressemble à ceci:

StructureMapConfiguration.ForRequestedType<MyClass>().TheDefaultIsConcreteType<MyClass>();
StructureMapConfiguration.ForRequestedType<IMyService>().TheDefaultIsConcreteType<SomeConcreteService>();

Donc, ce que vous avez fait, c'est dire au conteneur: "Quand quelqu'un demande IMyService, donnez-lui une copie de SomeConcreteService." Et vous avez également précisé que lorsque quelqu'un demande une MyClass, il obtient une MyClass concrète.

C'est tout ce qu'un conteneur IoC fait vraiment. Ils peuvent faire plus, mais c'est l'essentiel - ils résolvent les dépendances pour vous, vous n'avez donc pas à le faire (et vous n'avez pas à utiliser le mot-clé "new" dans tout votre code).

Dernière étape: lorsque vous créez votre MyClass, vous procédez comme suit:

var myClass = ObjectFactory.GetInstance<MyClass>();

J'espère que cela pourra aider. N'hésitez pas à m'envoyer un e-mail.

Chris Holmes
la source
2
Alors c'est comme une usine, je suppose? Si je suis cela correctement, n'utiliseriez-vous pas <IMyClass> au lieu de <MyClass> dans l'exemple final? donc ce serait var myClass = ObjectFactory.GetInstance <IMyClass> ()? Merci pour votre aide, c'est un bon début pour moi!
Ryan Abbott
3
D'une certaine manière, c'est comme une usine, oui. Une usine maîtresse pour votre application. Mais il peut être configuré pour renvoyer de nombreux types différents, y compris des singletons. En ce qui concerne l'interface avec MyClass - s'il s'agit d'un objet métier, je n'extraireais pas d'interface. Pour tout le reste, je le ferais généralement.
Chris Holmes
Et si vous appeliez uniquement ObjectFactory.GetInstance <MyClass> (); et vous n'avez pas configuré le SomeConcreteClass? Auriez-vous une erreur dans ce cas?
RayLoveless
1
@Ray: Cela dépend du conteneur. Certains conteneurs sont écrits de sorte que, par défaut, ils utilisent une convention de dénomination, de sorte que si une classe est nommée MyClass et que l'interface est nommée IMyInterface, le conteneur configurera automatiquement cette classe pour cette interface. Donc dans ce cas, si vous ne le configurez pas manuellement, la "convention" par défaut du conteneur le récupère quand même. Cependant, si votre classe et votre interface ne suivent pas la convention et que vous ne configurez pas le conteneur pour cette classe, alors oui, vous obtenez une erreur lors de l'exécution.
Chris Holmes
1
@saravanan Je pense que StructureMap fait maintenant une convention basée sur les noms. Je ne suis pas certain; nous ne l'avons pas utilisé depuis longtemps (j'en ai écrit un personnalisé pour notre entreprise; il utilise la convention du même nom pour les interfaces et les classes).
Chris Holmes
39

Je viens de regarder le screencast IoC d'injection de dépendance Unity de 30 minutes de David Hayden et j'ai pensé que c'était une bonne explication avec des exemples. Voici un extrait des notes de l'émission:

La capture d'écran montre plusieurs utilisations courantes de Unity IoC, telles que:

  • Création de types hors conteneur
  • Enregistrement et résolution des mappages de type
  • Enregistrement et résolution des mappages de types nommés
  • Singletons, LifetimeManagers et ContainerControlledLifetimeManager
  • Enregistrement d'instances existantes
  • Injection de dépendances dans des instances existantes
  • Remplir le UnityContainer via App.config / Web.config
  • Spécification des dépendances via l'API d'injection par opposition aux attributs de dépendance
  • Utilisation de conteneurs imbriqués (parent-enfant)
Kevin Hakanson
la source
32

Unity est une bibliothèque comme beaucoup d'autres qui vous permet d'obtenir une instance d'un type demandé sans avoir à la créer vous-même. Alors donné.

public interface ICalculator
{
    void Add(int a, int b);
}

public class Calculator : ICalculator
{
    public void Add(int a, int b)
    {
        return a + b;
    }
}

Vous utiliseriez une bibliothèque comme Unity pour enregistrer la calculatrice à renvoyer lorsque le type ICalculator est demandé aka IoC (Inversion of Control) (cet exemple est théorique, pas techniquement correct).

IoCLlibrary.Register<ICalculator>.Return<Calculator>();

Alors maintenant, quand vous voulez une instance d'un ICalculator, vous venez de ...

Calculator calc = IoCLibrary.Resolve<ICalculator>();

Les bibliothèques IoC peuvent généralement être configurées pour contenir un singleton ou créer une nouvelle instance chaque fois que vous résolvez un type.

Maintenant, disons que vous avez une classe qui repose sur un ICalculator pour être présent que vous pourriez avoir.

public class BankingSystem
{
    public BankingSystem(ICalculator calc)
    {
        _calc = calc;
    }

    private ICalculator _calc;
}

Et vous pouvez configurer la bibliothèque pour injecter un objet dans le constructeur lors de sa création.

Ainsi, DI ou injection de dépendances signifie injecter tout objet dont un autre pourrait avoir besoin.

Chad Moran
la source
devrait être ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Shukhrat Raimov
10

Unity est un IoC. Le but d'IoC est d'abstraire le câblage des dépendances entre les types en dehors des types eux-mêmes. Cela présente quelques avantages. Tout d'abord, cela se fait de manière centralisée, ce qui signifie que vous n'avez pas à changer beaucoup de code lorsque les dépendances changent (ce qui peut être le cas pour les tests unitaires).

De plus, si le câblage est effectué à l'aide de données de configuration au lieu de code, vous pouvez en fait recâbler les dépendances après le déploiement et ainsi modifier le comportement de l'application sans changer le code.

Brian Rasmussen
la source
5

MSDN a un guide du développeur sur l'injection de dépendances à l'aide de Unity qui peut être utile.

Le Guide du développeur commence par les bases de ce qu'est l'injection de dépendances et continue avec des exemples d'utilisation d'Unity pour l'injection de dépendances. Depuis février 2014, le Guide du développeur couvre Unity 3.0, qui a été publié en avril 2013.

Simon Tewsi
la source
1

Je couvre la plupart des exemples d'injection de dépendances dans l'API Web ASP.NET 2

public interface IShape
{
    string Name { get; set; }
}

public class NoShape : IShape
{
    public string Name { get; set; } = "I have No Shape";
}

public class Circle : IShape
{
    public string Name { get; set; } = "Circle";
}

public class Rectangle : IShape
{
    public Rectangle(string name)
    {
        this.Name = name;
    }

    public string Name { get; set; } = "Rectangle";
}

Dans DIAutoV2Controller.cs, le mécanisme d'injection automatique est utilisé

[RoutePrefix("api/v2/DIAutoExample")]
public class DIAutoV2Controller : ApiController
{
    private string ConstructorInjected;
    private string MethodInjected1;
    private string MethodInjected2;
    private string MethodInjected3;

    [Dependency]
    public IShape NoShape { get; set; }

    [Dependency("Circle")]
    public IShape ShapeCircle { get; set; }

    [Dependency("Rectangle")]
    public IShape ShapeRectangle { get; set; }

    [Dependency("PiValueExample1")]
    public double PiValue { get; set; }

    [InjectionConstructor]
    public DIAutoV2Controller([Dependency("Circle")]IShape shape1, [Dependency("Rectangle")]IShape shape2, IShape shape3)
    {
        this.ConstructorInjected = shape1.Name + " & " + shape2.Name + " & " + shape3.Name;
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize()
    {
        this.MethodInjected1 = "Default Initialize done";
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize2([Dependency("Circle")]IShape shape1)
    {
        this.MethodInjected2 = shape1.Name;
    }

    [NonAction]
    [InjectionMethod]
    public void Initialize3(IShape shape1)
    {
        this.MethodInjected3 = shape1.Name;
    }

    [HttpGet]
    [Route("constructorinjection")]
    public string constructorinjection()
    {
        return "Constructor Injected: " + this.ConstructorInjected;
    }

    [HttpGet]
    [Route("GetNoShape")]
    public string GetNoShape()
    {
        return "Property Injected: " + this.NoShape.Name;
    }

    [HttpGet]
    [Route("GetShapeCircle")]
    public string GetShapeCircle()
    {
        return "Property Injected: " + this.ShapeCircle.Name;
    }

    [HttpGet]
    [Route("GetShapeRectangle")]
    public string GetShapeRectangle()
    {
        return "Property Injected: " + this.ShapeRectangle.Name;
    }

    [HttpGet]
    [Route("GetPiValue")]
    public string GetPiValue()
    {
        return "Property Injected: " + this.PiValue;
    }

    [HttpGet]
    [Route("MethodInjected1")]
    public string InjectionMethod1()
    {
        return "Method Injected: " + this.MethodInjected1;
    }

    [HttpGet]
    [Route("MethodInjected2")]
    public string InjectionMethod2()
    {
        return "Method Injected: " + this.MethodInjected2;
    }

    [HttpGet]
    [Route("MethodInjected3")]
    public string InjectionMethod3()
    {
        return "Method Injected: " + this.MethodInjected3;
    }
}

Dans DIV2Controller.cs, tout sera injecté à partir de la classe Dependency Configuration Resolver

[RoutePrefix("api/v2/DIExample")]
public class DIV2Controller : ApiController
{
    private string ConstructorInjected;
    private string MethodInjected1;
    private string MethodInjected2;
    public string MyPropertyName { get; set; }
    public double PiValue1 { get; set; }
    public double PiValue2 { get; set; }
    public IShape Shape { get; set; }

    // MethodInjected
    [NonAction]
    public void Initialize()
    {
        this.MethodInjected1 = "Default Initialize done";
    }

    // MethodInjected
    [NonAction]
    public void Initialize2(string myproperty1, IShape shape1, string myproperty2, IShape shape2)
    {
        this.MethodInjected2 = myproperty1 + " & " + shape1.Name + " & " + myproperty2 + " & " + shape2.Name;
    }

    public DIV2Controller(string myproperty1, IShape shape1, string myproperty2, IShape shape2)
    {
        this.ConstructorInjected = myproperty1 + " & " + shape1.Name + " & " + myproperty2 + " & " + shape2.Name;
    }

    [HttpGet]
    [Route("constructorinjection")]
    public string constructorinjection()
    {
        return "Constructor Injected: " + this.ConstructorInjected;
    }

    [HttpGet]
    [Route("PropertyInjected")]
    public string InjectionProperty()
    {
        return "Property Injected: " + this.MyPropertyName;
    }

    [HttpGet]
    [Route("GetPiValue1")]
    public string GetPiValue1()
    {
        return "Property Injected: " + this.PiValue1;
    }

    [HttpGet]
    [Route("GetPiValue2")]
    public string GetPiValue2()
    {
        return "Property Injected: " + this.PiValue2;
    }

    [HttpGet]
    [Route("GetShape")]
    public string GetShape()
    {
        return "Property Injected: " + this.Shape.Name;
    }

    [HttpGet]
    [Route("MethodInjected1")]
    public string InjectionMethod1()
    {
        return "Method Injected: " + this.MethodInjected1;
    }

    [HttpGet]
    [Route("MethodInjected2")]
    public string InjectionMethod2()
    {
        return "Method Injected: " + this.MethodInjected2;
    }
}

Configuration du résolveur de dépendances

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    RegisterInterfaces(container);
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

private static void RegisterInterfaces(UnityContainer container)
{
    var dbContext = new SchoolDbContext();
    // Registration with constructor injection
    container.RegisterType<IStudentRepository, StudentRepository>(new InjectionConstructor(dbContext));
    container.RegisterType<ICourseRepository, CourseRepository>(new InjectionConstructor(dbContext));

    // Set constant/default value of Pi = 3.141 
    container.RegisterInstance<double>("PiValueExample1", 3.141);
    container.RegisterInstance<double>("PiValueExample2", 3.14);

    // without a name
    container.RegisterInstance<IShape>(new NoShape());

    // with circle name
    container.RegisterType<IShape, Circle>("Circle", new InjectionProperty("Name", "I am Circle"));

    // with rectangle name
    container.RegisterType<IShape, Rectangle>("Rectangle", new InjectionConstructor("I am Rectangle"));

    // Complex type like Constructor, Property and method injection
    container.RegisterType<DIV2Controller, DIV2Controller>(
        new InjectionConstructor("Constructor Value1", container.Resolve<IShape>("Circle"), "Constructor Value2", container.Resolve<IShape>()),
        new InjectionMethod("Initialize"),
        new InjectionMethod("Initialize2", "Value1", container.Resolve<IShape>("Circle"), "Value2", container.Resolve<IShape>()),
        new InjectionProperty("MyPropertyName", "Property Value"),
        new InjectionProperty("PiValue1", container.Resolve<double>("PiValueExample1")),
        new InjectionProperty("Shape", container.Resolve<IShape>("Rectangle")),
        new InjectionProperty("PiValue2", container.Resolve<double>("PiValueExample2")));
}
Narottam Goyal
la source
Ce n'est pas une réponse particulièrement utile pour plusieurs raisons. C'est un exemple inutilement complexe qui contient trop de code pour être utile pour offrir une explication simple d'IOC. De plus, le code n'est pas clairement documenté aux endroits où vous en auriez réellement besoin.
Dan Atkinson le