¿Alguien puede explicar Microsoft Unity?

157

He estado leyendo los artículos en MSDN sobre Unity (Inyección de dependencia, Inversión de control), pero creo que necesito explicarlo en términos simples (o ejemplos simples). Estoy familiarizado con el patrón MVPC (lo usamos aquí), pero todavía no puedo entender esto de Unity, y creo que es el siguiente paso en el diseño de nuestra aplicación.

Ryan Abbott
fuente
12
Me encanta cómo tiene el mismo nombre que "Unity", así que cuando estoy buscando cosas de Unity Game Engine veo esta vieja tecnología, suspiro. Todos los buenos nombres de la banda están tomados, supongo.
Tom Schulz
2
@ Tom-Schulz Vieja tecnología? nuget.org/packages/Unity - última actualización hace 5 días.
Roger Willcocks

Respuestas:

174

La unidad es solo un "contenedor" de IoC. Google StructureMap y pruébalo en su lugar. Creo que es un poco más fácil de asimilar cuando las cosas de IoC son nuevas para ti.

Básicamente, si comprende IoC, entonces comprende que lo que está haciendo es invertir el control para cuando se crea un objeto.

Sin IoC:

public class MyClass
{
   IMyService _myService; 

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

Con contenedor IoC:

public class MyClass
{
   IMyService _myService; 

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

Sin IoC, su clase que se basa en IMyService tiene que actualizar una versión concreta del servicio para usar. Y eso es malo por varias razones (ha acoplado su clase a una versión concreta específica del IMyService, no puede probarlo fácilmente, no puede cambiarlo fácilmente, etc.)

Con un contenedor de IoC, "configura" el contenedor para resolver esas dependencias por usted. Entonces, con un esquema de inyección basado en el constructor, simplemente pasa la interfaz a la dependencia IMyService en el constructor. Cuando cree MyClass con su contenedor, su contenedor resolverá la dependencia de IMyService por usted.

Usando StructureMap, la configuración del contenedor se ve así:

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

Entonces, lo que ha hecho se le dice al contenedor: "Cuando alguien solicite el IMyService, entréguele una copia de SomeConcreteService". Y también ha especificado que cuando alguien solicita un MyClass, obtiene un MyClass concreto.

Eso es todo lo que un contenedor de IoC realmente hace. Pueden hacer más, pero ese es el objetivo: resuelven las dependencias por usted, por lo que no tiene que hacerlo (y no tiene que usar la palabra clave "nueva" en todo el código).

Paso final: cuando crea su MyClass, haría esto:

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

Espero que ayude. No dude en enviarme un correo electrónico.

Chris Holmes
fuente
2
Entonces, ¿es como una fábrica, supongo? Si sigo esto correctamente, ¿no usarías <IMyClass> en lugar de <MyClass> en el ejemplo final? entonces sería var myClass = ObjectFactory.GetInstance <IMyClass> ()? ¡Gracias por su ayuda, este es un buen comienzo para mí!
Ryan Abbott
3
En cierto modo, es como una fábrica, sí. Una fábrica maestra para su aplicación. Pero se puede configurar para devolver muchos tipos diferentes, incluidos los singletons. En cuanto a la interfaz de MyClass, si se trata de un objeto comercial, no extraería una interfaz. Por todo lo demás, generalmente lo haría.
Chris Holmes
¿Qué pasa si solo llamó a ObjectFactory.GetInstance <MyClass> (); y no configuraste SomeConcreteClass? ¿Conseguirías un error en ese caso?
RayLoveless
1
@Ray: depende del contenedor. Algunos contenedores están escritos de manera que, de manera predeterminada, utilizan una convención de nomenclatura, de modo que si una clase se llama MyClass y la interfaz se llama IMyInterface, el contenedor configurará automáticamente esa clase para esa interfaz. Entonces, en ese caso, si no lo configura manualmente, la "convención" predeterminada del contenedor lo recoge de todos modos. Sin embargo, si su clase e interfaz no siguen la convención y no configura el contenedor para esa clase, entonces sí, recibirá un error en tiempo de ejecución.
Chris Holmes
1
@saravanan Creo que StructureMap hace una convención basada en nombres ahora. No estoy seguro; no lo hemos usado en mucho tiempo (escribí uno personalizado para nuestro negocio; usa la convención del mismo nombre para interfaces y clases).
Chris Holmes
39

Acabo de ver el Screencast de 30 minutos de inyección de dependencia de unidad de IoC de David Hayden y sentí que era una buena explicación con ejemplos. Aquí hay un fragmento de las notas del programa:

El screencast muestra varios usos comunes de Unity IoC, tales como:

  • Crear tipos que no están en el contenedor
  • Registrar y resolver TypeMappings
  • Registro y resolución de asignaciones de tipos con nombre
  • Singletons, LifetimeManagers y ContainerControlledLifetimeManager
  • Registrar instancias existentes
  • Inyección de dependencias en instancias existentes
  • Rellenar UnityContainer a través de App.config / Web.config
  • Especificar dependencias mediante API de inyección en lugar de atributos de dependencia
  • Uso de contenedores anidados (padre-hijo)
Kevin Hakanson
fuente
32

Unity es una biblioteca como muchas otras que le permite obtener una instancia de un tipo solicitado sin tener que crearla usted mismo. Así dado.

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

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

Usaría una biblioteca como Unity para registrar la Calculadora que se devolverá cuando se solicite el tipo ICalculator, también conocido como IoC (Inversión de control) (este ejemplo es teórico, no técnicamente correcto).

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

Entonces, cuando quieres una instancia de un ICalculator, simplemente ...

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

Las bibliotecas de IoC generalmente se pueden configurar para contener un singleton o crear una nueva instancia cada vez que resuelva un tipo.

Ahora supongamos que tiene una clase que se basa en un ICalculator para estar presente que podría tener ...

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

    private ICalculator _calc;
}

Y puede configurar la biblioteca para inyectar un objeto en el constructor cuando se crea.

Por lo tanto, DI o inyección de dependencia significa inyectar cualquier objeto que otro pueda necesitar.

Chad Moran
fuente
debe ser ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Shukhrat Raimov
10

La unidad es un IoC. El objetivo de IoC es abstraer el cableado de dependencias entre tipos fuera de los tipos mismos. Esto tiene un par de ventajas. En primer lugar, se realiza de forma centralizada, lo que significa que no tiene que cambiar mucho código cuando cambian las dependencias (que puede ser el caso de las pruebas unitarias).

Además, si el cableado se realiza utilizando datos de configuración en lugar de código, puede volver a cablear las dependencias después de la implementación y, por lo tanto, cambiar el comportamiento de la aplicación sin cambiar el código.

Brian Rasmussen
fuente
5

MSDN tiene una Guía del desarrollador para la inyección de dependencias utilizando Unity que puede ser útil.

La Guía del desarrollador comienza con los conceptos básicos de qué es la inyección de dependencia y continúa con ejemplos de cómo usar Unity para la inyección de dependencia. A partir de febrero de 2014, la Guía del desarrollador cubre Unity 3.0, que se lanzó en abril de 2013.

Simon Tewsi
fuente
1

Estoy cubriendo la mayoría de los ejemplos de Inyección de dependencias en ASP.NET Web API 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";
}

En DIAutoV2Controller.cs se utiliza el mecanismo de inyección automática

[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;
    }
}

En DIV2Controller.cs todo se inyectará desde la clase de resolución de configuración de dependencias

[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;
    }
}

Configurar el solucionador de dependencias

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
fuente
Esta no es una respuesta particularmente útil por varias razones. Es un ejemplo innecesariamente complejo que tiene demasiado código para ser útil al ofrecer una explicación simple de IOC. Además de eso, el código no está documentado claramente en lugares donde realmente lo necesitarías.
Dan Atkinson