Alguém pode explicar o Microsoft Unity?

157

Eu tenho lido os artigos no MSDN sobre Unity (injeção de dependência, inversão de controle), mas acho que preciso explicá-lo em termos simples (ou exemplos simples). Eu estou familiarizado com o padrão MVPC (nós o usamos aqui), mas ainda não consigo entender essa coisa do Unity ainda, e acho que é o próximo passo no design do aplicativo.

Ryan Abbott
fonte
12
Eu amo como isso tem o mesmo nome que "Unity", então, quando estou pesquisando coisas do Unity Game Engine, vejo essa tecnologia antiga, suspiro. Todos os bons nomes de bandas são usados, eu acho.
Tom Schulz
2
@ tom-schulz Tecnologia antiga? nuget.org/packages/Unity - última atualização 5 dias atrás.
Roger Willcocks

Respostas:

174

A unidade é apenas um "contêiner" de IoC. Google StructureMap e experimente. Um pouco mais fácil de entender, eu acho, quando as coisas de IoC são novas para você.

Basicamente, se você entende de IoC, entende que o que está fazendo é inverter o controle para quando um objeto é criado.

Sem IoC:

public class MyClass
{
   IMyService _myService; 

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

Com contêiner de IoC:

public class MyClass
{
   IMyService _myService; 

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

Sem o IoC, sua classe que depende do IMyService precisa atualizar uma versão concreta do serviço a ser usada. E isso é ruim por vários motivos (você associou sua classe a uma versão concreta específica do IMyService, não pode testá-lo facilmente, não pode alterá-lo facilmente etc.)

Com um contêiner de IoC, você "configura" o contêiner para resolver essas dependências para você. Portanto, com um esquema de injeção baseado em construtor, basta passar a interface para a dependência IMyService no construtor. Quando você cria o MyClass com seu contêiner, ele resolverá a dependência do IMyService para você.

Usando o StructureMap, a configuração do contêiner fica assim:

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

Portanto, o que você fez foi informado ao contêiner: "Quando alguém solicitar o IMyService, dê uma cópia do SomeConcreteService". E você também especificou que, quando alguém pede uma MyClass, ela recebe uma MyClass concreta.

Isso é tudo que um contêiner de IoC realmente faz. Eles podem fazer mais, mas esse é o objetivo - eles resolvem dependências para você, então você não precisa (e não precisa usar a palavra-chave "nova" em todo o código).

Etapa final: ao criar seu MyClass, você faria o seguinte:

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

Espero que ajude. Fique a vontade para me mandar um e-mail.

Chris Holmes
fonte
2
Então é como uma fábrica, suponho? Se estou seguindo isso corretamente, você não usaria <IMyClass> em vez de <MyClass> no exemplo final? então seria var myClass = ObjectFactory.GetInstance <IMyClass> ()? Obrigado pela ajuda, este é um bom começo para mim!
23909 Ryan Abbott
3
De certa forma, é como uma fábrica, sim. Uma fábrica principal para sua aplicação. Mas pode ser configurado para retornar muitos tipos diferentes, incluindo singletons. Quanto à interface para MyClass - se for um objeto de negócios, eu não extrairia uma interface. Para todo o resto, eu geralmente faria.
31119 Chris Holmes
e se você chamasse apenas ObjectFactory.GetInstance <MyClass> (); e você não configurou a SomeConcreteClass? Você receberia um erro nesse caso?
precisa saber é o seguinte
1
@ Ray: Depende do recipiente. Alguns contêineres são gravados para que, por padrão, eles usem uma convenção de nomenclatura, de modo que, se uma classe for chamada MyClass e a interface for denominada IMyInterface, o contêiner configurará automaticamente essa classe para essa interface. Portanto, nesse caso, se você não o configurar manualmente, a "convenção" padrão do contêiner o coletará de qualquer maneira. No entanto, se sua classe e interface não seguirem a convenção e você não configurar o contêiner para essa classe, sim, você receberá um erro no tempo de execução.
21813 Chris Holmes
1
@saravanan Acho que o StructureMap faz uma convenção baseada em nome agora. Não tenho certeza; não o usamos há muito tempo (escrevi um personalizado para nossos negócios; ele usa a convenção de mesmo nome para interfaces e classes).
Chris Holmes
39

Acabei de assistir ao Screencast de IoC de injeção de dependência de unidade de 30 minutos de David Hayden e achei que era uma boa explicação com exemplos. Aqui está um trecho das notas do programa:

O screencast mostra vários usos comuns do Unity IoC, como:

  • Criando tipos que não estão no contêiner
  • Registrando e resolvendo TypeMappings
  • Registrando e resolvendo aplicativos de tipo nomeado
  • Singletons, LifetimeManagers e o ContainerControlledLifetimeManager
  • Registrando Instâncias Existentes
  • Injetando dependências em instâncias existentes
  • Preenchendo o UnityContainer via App.config / Web.config
  • Especificando dependências por meio da API de injeção, em oposição aos atributos de dependência
  • Usando contêineres aninhados (pai-filho)
Kevin Hakanson
fonte
32

O Unity é uma biblioteca como muitas outras que permite obter uma instância de um tipo solicitado sem precisar criar você mesma. Tão dado.

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

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

Você usaria uma biblioteca como o Unity para registrar a Calculadora a ser retornada quando o tipo ICalculator for solicitado, também conhecido como IoC (Inversion of Control) (este exemplo é teórico, não tecnicamente correto).

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

Então agora, quando você quer uma instância de um ICalculator, apenas ...

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

As bibliotecas de IoC geralmente podem ser configuradas para reter um singleton ou criar uma nova instância sempre que você resolver um tipo.

Agora, digamos que você tenha uma classe que depende de um ICalculator para estar presente que você poderia ter ..

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

    private ICalculator _calc;
}

E você pode configurar a biblioteca para injetar um objeto no construtor quando ele é criado.

Portanto, DI ou injeção de dependência significa injetar qualquer objeto que outro possa exigir.

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

A unidade é uma IoC. O objetivo da IoC é abstrair a fiação das dependências entre os tipos fora dos próprios tipos. Isso tem algumas vantagens. Antes de tudo, isso é feito centralmente, o que significa que você não precisa alterar muito código quando as dependências mudam (o que pode ser o caso para testes de unidade).

Além disso, se a fiação for feita usando dados de configuração em vez de código, você poderá reconectar as dependências após a implantação e, assim, alterar o comportamento do aplicativo sem alterar o código.

Brian Rasmussen
fonte
5

O MSDN possui um Guia do desenvolvedor para injeção de dependência usando o Unity que pode ser útil.

O Guia do desenvolvedor começa com o básico sobre o que é injeção de dependência e continua com exemplos de como usar o Unity para injeção de dependência. Em fevereiro de 2014, o Guia do desenvolvedor abrange o Unity 3.0, lançado em abril de 2013.

Simon Tewsi
fonte
1

Estou abordando a maioria dos exemplos de injeção de dependência na API da Web do 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";
}

No DIAutoV2Controller.cs, o mecanismo de injeção automática é usado

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

No DIV2Controller.cs, tudo será injetado da classe Resolver Configuration Configuration

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

Configurando o resolvedor de dependências

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
fonte
Esta não é uma resposta particularmente útil por vários motivos. É um exemplo desnecessariamente complexo que possui muito código para ser útil, oferecendo uma explicação simples do COI. Além disso, o código não está documentado claramente em locais onde você realmente precisa.
Dan Atkinson