Czy ktoś może wyjaśnić Microsoft Unity?

157

Czytałem artykuły na MSDN o Unity (Dependency Injection, Inversion of Control), ale myślę, że potrzebuję wyjaśnienia w prostych słowach (lub prostych przykładach). Znam wzorzec MVPC (używamy go tutaj), ale po prostu nie mogę jeszcze zrozumieć tej rzeczy z Unity i myślę, że to kolejny krok w naszym projektowaniu aplikacji.

Ryan Abbott
źródło
12
Uwielbiam to, że ma taką samą nazwę jak „Unity”, więc kiedy szukam rzeczy z Unity Game Engine, widzę tę starą technologię, westchnij. Myślę, że wszystkie dobre nazwy zespołów są zajęte.
Tom Schulz
2
@ tom-schulz Stara technologia? nuget.org/packages/Unity - ostatnia aktualizacja 5 dni temu.
Roger Willcocks

Odpowiedzi:

174

Unity to tylko „kontener” IoC. Google StructureMap i zamiast tego wypróbuj. Myślę, że trochę łatwiejsze do zrozumienia, kiedy rzeczy IoC są dla ciebie nowe.

Zasadniczo, jeśli rozumiesz IoC, rozumiesz, że to, co robisz, to odwracanie kontroli, kiedy obiekt zostanie utworzony.

Bez IoC:

public class MyClass
{
   IMyService _myService; 

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

Z kontenerem IoC:

public class MyClass
{
   IMyService _myService; 

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

Bez IoC Twoja klasa, która korzysta z IMyService, będzie musiała wprowadzić nową konkretną wersję usługi do użycia. Jest to złe z wielu powodów (powiązałeś swoją klasę z konkretną konkretną wersją IMyService, nie możesz jej łatwo przetestować jednostkowo, nie możesz jej łatwo zmienić itp.)

Za pomocą kontenera IoC „konfigurujesz” kontener, aby rozwiązać te zależności za Ciebie. Tak więc w przypadku schematu iniekcji opartego na konstruktorze wystarczy przekazać interfejs do zależności IMyService do konstruktora. Gdy utworzysz MyClass z kontenerem, Twój kontener rozwiąże za Ciebie zależność IMyService.

Korzystając ze StructureMap, konfiguracja kontenera wygląda następująco:

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

Więc to, co zrobiłeś, to powiedzenie kontenerowi: „Gdy ktoś zażąda usługi IMyService, przekaż mu kopię SomeConcreteService”. Określono również, że gdy ktoś prosi o MyClass, otrzymuje konkretną MyClass.

To wszystko, co naprawdę robi kontener IoC. Mogą zrobić więcej, ale o to właśnie chodzi - rozwiązują zależności za Ciebie, więc Ty nie musisz (i nie musisz używać słowa kluczowego „new” w całym kodzie).

Ostatni krok: kiedy tworzysz MyClass, zrobiłbyś to:

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

Mam nadzieję, że to pomoże. Zapraszam do e-maila.

Chris Holmes
źródło
2
Więc to jak fabryka, jak sądzę? Jeśli postępuję zgodnie z tym poprawnie, czy nie użyłbyś <IMyClass> zamiast <MyClass> w ostatnim przykładzie? więc byłoby var myClass = ObjectFactory.GetInstance <IMyClass> ()? Dziękuję za pomoc, to dla mnie dobry początek!
Ryan Abbott,
3
W pewnym sensie to jak fabryka, tak. Mistrzowska fabryka dla Twojej aplikacji. Można go jednak skonfigurować tak, aby zwracał wiele różnych typów, w tym pojedyncze. Jeśli chodzi o interfejs do MyClass - jeśli jest to obiekt biznesowy, nie wyodrębniałbym interfejsu. W przypadku wszystkiego innego generalnie tak.
Chris Holmes,
co by było, gdybyś wywołał tylko ObjectFactory.GetInstance <MyClass> (); i nie skonfigurowałeś SomeConcreteClass? Czy w takim przypadku otrzymasz błąd?
RayLoveless
1
@Ray: To zależy od kontenera. Niektóre kontenery są napisane w taki sposób, że domyślnie używają konwencji nazewnictwa, na przykład jeśli klasa nosi nazwę MyClass, a interfejs nosi nazwę IMyInterface, kontener automatycznie skonfiguruje tę klasę dla tego interfejsu. W takim przypadku, jeśli nie skonfigurujesz go ręcznie, domyślna „konwencja” kontenera i tak ją przejmie. Jeśli jednak Twoja klasa i interfejs nie są zgodne z konwencją i nie skonfigurujesz kontenera dla tej klasy, to tak, otrzymasz błąd w czasie wykonywania.
Chris Holmes,
1
@saravanan Myślę, że StructureMap teraz wykonuje konwencję opartą na nazwach. Nie jestem pewny; nie używaliśmy go od dawna (napisałem niestandardowy dla naszej firmy; używa konwencji tej samej nazwy dla interfejsów i klas).
Chris Holmes
39

Właśnie obejrzałem 30-minutowy zrzut ekranu IoC o Unity Dependency Injection Davida Haydena i uznałem, że to dobre wyjaśnienie z przykładami. Oto fragment notatek z programu:

Screencast przedstawia kilka typowych zastosowań Unity IoC, takich jak:

  • Tworzenie typów bez kontenera
  • Rejestrowanie i rozwiązywanie typów mapowań
  • Rejestrowanie i rozwiązywanie nazwanych mapowań typów
  • Singletons, LifetimeManagers i ContainerControlledLifetimeManager
  • Rejestracja istniejących wystąpień
  • Wstrzykiwanie zależności do istniejących instancji
  • Wypełnianie UnityContainer za pośrednictwem App.config / Web.config
  • Określanie zależności za pośrednictwem interfejsu API iniekcji w przeciwieństwie do atrybutów zależności
  • Korzystanie z kontenerów zagnieżdżonych (nadrzędny-podrzędny)
Kevin Hakanson
źródło
32

Unity to biblioteka, jak wiele innych, która umożliwia uzyskanie instancji żądanego typu bez konieczności jej samodzielnego tworzenia. Tak biorąc.

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

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

Możesz użyć biblioteki, takiej jak Unity, do zarejestrowania Kalkulatora, który ma zostać zwrócony, gdy żądany jest typ ICalculator, czyli IoC (Odwrócenie kontroli) (ten przykład jest teoretyczny, nie jest technicznie poprawny).

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

Więc teraz, gdy potrzebujesz instancji ICalculator, po prostu ...

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

Biblioteki IoC można zwykle skonfigurować tak, aby zawierały pojedyncze egzemplarze lub tworzyły nową instancję za każdym razem, gdy rozwiążesz typ.

Teraz powiedzmy, że masz klasę, która opiera się na ICalculator, który jest obecny, co możesz mieć ...

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

    private ICalculator _calc;
}

I możesz skonfigurować bibliotekę, aby wstrzyknąć obiekt do konstruktora po jego utworzeniu.

Tak więc DI lub Dependency Injection oznacza wstrzyknięcie dowolnego obiektu, którego może wymagać inny.

Chad Moran
źródło
powinno być ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Shukhrat Raimov
10

Jedność to IoC. Celem IoC jest wyodrębnienie okablowania zależności między typami poza samymi typami. Ma to kilka zalet. Przede wszystkim odbywa się to centralnie, co oznacza, że ​​nie musisz zmieniać dużej ilości kodu, gdy zmieniają się zależności (co może mieć miejsce w przypadku testów jednostkowych).

Ponadto, jeśli okablowanie jest wykonywane przy użyciu danych konfiguracyjnych zamiast kodu, można faktycznie ponownie okablować zależności po wdrożeniu, a tym samym zmienić zachowanie aplikacji bez zmiany kodu.

Brian Rasmussen
źródło
1

Omawiam większość przykładów iniekcji zależności w 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";
}

W DIAutoV2Controller.cs zastosowano mechanizm Auto Injection

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

W DIV2Controller.cs wszystko zostanie wstrzyknięte z klasy 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;
    }
}

Konfigurowanie mechanizmu rozpoznawania zależności

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
źródło
Nie jest to szczególnie przydatna odpowiedź z wielu powodów. Jest to niepotrzebnie złożony przykład, który zawiera zbyt dużo kodu, aby był przydatny w oferowaniu prostego wyjaśnienia IOC. Poza tym kod nie jest wyraźnie udokumentowany w miejscach, w których faktycznie go potrzebujesz.
Dan Atkinson