Kann jemand Microsoft Unity erklären?

157

Ich habe die Artikel auf MSDN über Unity (Dependency Injection, Inversion of Control) gelesen, aber ich denke, ich muss sie in einfachen Worten (oder einfachen Beispielen) erklären. Ich bin mit dem MVPC-Muster vertraut (wir verwenden es hier), aber ich kann dieses Unity-Ding noch nicht wirklich verstehen, und ich denke, es ist der nächste Schritt in unserem Anwendungsdesign.

Ryan Abbott
quelle
12
Ich liebe es, wie dies den gleichen Namen wie "Unity" hat. Wenn ich also nach Unity Game Engine-Sachen suche, sehe ich diese alte Technologie, seufz. Alle guten Bandnamen sind vergeben, denke ich.
Tom Schulz
2
@ tom-schulz Alte Technik? nuget.org/packages/Unity - zuletzt aktualisiert vor 5 Tagen.
Roger Willcocks

Antworten:

174

Unity ist nur ein IoC "Container". Google StructureMap und probieren Sie es stattdessen aus. Ein bisschen einfacher zu grillen, denke ich, wenn das IoC-Zeug für dich neu ist.

Wenn Sie IoC verstehen, verstehen Sie im Grunde, dass Sie das Steuerelement umkehren, wenn ein Objekt erstellt wird.

Ohne IoC:

public class MyClass
{
   IMyService _myService; 

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

Mit IoC-Container:

public class MyClass
{
   IMyService _myService; 

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

Ohne IoC muss Ihre Klasse, die sich auf den IMyService stützt, eine konkrete Version des zu verwendenden Dienstes neu erstellen. Und das ist aus mehreren Gründen schlecht (Sie haben Ihre Klasse an eine bestimmte konkrete Version des IMyService gekoppelt, Sie können sie nicht einfach testen, Sie können sie nicht einfach ändern usw.)

Mit einem IoC-Container "konfigurieren" Sie den Container, um diese Abhängigkeiten für Sie aufzulösen. Bei einem konstruktorbasierten Injektionsschema übergeben Sie einfach die Schnittstelle an die IMyService-Abhängigkeit an den Konstruktor. Wenn Sie die MyClass mit Ihrem Container erstellen, löst Ihr Container die IMyService-Abhängigkeit für Sie auf.

Mit StructureMap sieht die Konfiguration des Containers folgendermaßen aus:

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

Sie haben dem Container also mitgeteilt: "Wenn jemand den IMyService anfordert, geben Sie ihm eine Kopie des SomeConcreteService." Und Sie haben auch angegeben, dass jemand, der nach einer MyClass fragt, eine konkrete MyClass erhält.

Das ist alles, was ein IoC-Container wirklich tut. Sie können mehr, aber das ist der Kern davon - sie lösen Abhängigkeiten für Sie auf, sodass Sie dies nicht tun müssen (und Sie müssen das Schlüsselwort "new" nicht in Ihrem gesamten Code verwenden).

Letzter Schritt: Wenn Sie Ihre MyClass erstellen, gehen Sie folgendermaßen vor:

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

Hoffentlich hilft das. Fühlen Sie sich frei, mir eine E-Mail zu senden.

Chris Holmes
quelle
2
Es ist also wie in einer Fabrik, nehme ich an? Wenn ich das richtig befolge, würden Sie im letzten Beispiel nicht <IMyClass> anstelle von <MyClass> verwenden? also wäre es var myClass = ObjectFactory.GetInstance <IMyClass> ()? Vielen Dank für Ihre Hilfe, das ist ein guter Anfang für mich!
Ryan Abbott
3
In gewisser Weise ist es wie eine Fabrik, ja. Eine Master-Fabrik für Ihre Anwendung. Es kann jedoch so konfiguriert werden, dass viele verschiedene Typen zurückgegeben werden, einschließlich Singletons. Was die Schnittstelle zu MyClass betrifft - wenn es sich um ein Geschäftsobjekt handelt, würde ich keine Schnittstelle extrahieren. Für alles andere würde ich im Allgemeinen.
Chris Holmes
Was ist, wenn Sie nur ObjectFactory.GetInstance <MyClass> () aufgerufen haben? und Sie haben die SomeConcreteClass nicht konfiguriert? Würden Sie in diesem Fall einen Fehler bekommen?
RayLoveless
1
@ Ray: Das hängt vom Container ab. Einige Container sind so geschrieben, dass sie standardmäßig eine Namenskonvention verwenden. Wenn also eine Klasse MyClass und die Schnittstelle IMyInterface heißt, konfiguriert der Container diese Klasse automatisch für diese Schnittstelle. Wenn Sie es in diesem Fall nicht manuell konfigurieren, wird es von der Standard- "Konvention" des Containers trotzdem übernommen. Wenn Ihre Klasse und Ihre Schnittstelle jedoch nicht der Konvention entsprechen und Sie den Container für diese Klasse nicht konfigurieren, wird zur Laufzeit ein Fehler angezeigt.
Chris Holmes
1
@saravanan Ich denke, StructureMap führt jetzt eine namenbasierte Konvention durch. Ich bin nicht sicher; Wir haben es schon lange nicht mehr verwendet (ich habe ein benutzerdefiniertes für unser Unternehmen geschrieben; es verwendet die gleichnamige Konvention für Schnittstellen und Klassen).
Chris Holmes
39

Ich habe gerade den 30-minütigen IoC-Screencast von David Hayden zur Unity Dependency Injection gesehen und fand, dass dies eine gute Erklärung mit Beispielen war. Hier ist ein Ausschnitt aus den Shownotizen:

Der Screencast zeigt verschiedene gängige Verwendungen des Unity IoC, wie zum Beispiel:

  • Erstellen von Typen, die sich nicht im Container befinden
  • Registrieren und Auflösen von TypeMappings
  • Registrieren und Auflösen benannter TypeMappings
  • Singletons, LifetimeManager und der ContainerControlledLifetimeManager
  • Registrieren vorhandener Instanzen
  • Injizieren von Abhängigkeiten in vorhandene Instanzen
  • Auffüllen des UnityContainer über App.config / Web.config
  • Festlegen von Abhängigkeiten über die Injection-API im Gegensatz zu Abhängigkeitsattributen
  • Verwenden verschachtelter (Eltern-Kind-) Container
Kevin Hakanson
quelle
32

Unity ist eine Bibliothek wie viele andere, mit der Sie eine Instanz eines angeforderten Typs abrufen können, ohne sie selbst erstellen zu müssen. So gegeben.

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

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

Sie würden eine Bibliothek wie Unity verwenden, um den Taschenrechner zu registrieren, der zurückgegeben werden soll, wenn der Typ ICalculator (IoC (Inversion of Control)) angefordert wird (dieses Beispiel ist theoretisch, technisch nicht korrekt).

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

Wenn Sie also eine Instanz eines ICalculators möchten, müssen Sie nur ...

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

IoC-Bibliotheken können normalerweise so konfiguriert werden, dass sie bei jedem Auflösen eines Typs entweder einen Singleton enthalten oder eine neue Instanz erstellen.

Nehmen wir jetzt an, Sie haben eine Klasse, die sich darauf verlässt, dass ein ICalculator vorhanden ist, den Sie haben könnten.

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

    private ICalculator _calc;
}

Sie können die Bibliothek so einrichten, dass beim Erstellen ein Objekt in den Konstruktor eingefügt wird.

DI oder Abhängigkeitsinjektion bedeutet also, jedes Objekt zu injizieren, das ein anderes möglicherweise benötigt.

Chad Moran
quelle
sollte ICalculator calc = IoCLibrary.Resolve <ICalculator> () sein;
Shukhrat Raimov
10

Einheit ist ein IoC. Der Zweck von IoC besteht darin, die Verdrahtung von Abhängigkeiten zwischen Typen außerhalb der Typen selbst zu abstrahieren. Dies hat einige Vorteile. Zuallererst erfolgt dies zentral, was bedeutet, dass Sie nicht viel Code ändern müssen, wenn sich Abhängigkeiten ändern (was bei Komponententests der Fall sein kann).

Wenn die Verkabelung mithilfe von Konfigurationsdaten anstelle von Code erfolgt, können Sie die Abhängigkeiten nach der Bereitstellung tatsächlich neu verkabeln und so das Verhalten der Anwendung ändern, ohne den Code zu ändern.

Brian Rasmussen
quelle
5

MSDN verfügt über ein Entwicklerhandbuch zur Abhängigkeitsinjektion mit Unity , das möglicherweise hilfreich ist.

Das Entwicklerhandbuch beginnt mit den Grundlagen der Abhängigkeitsinjektion und enthält Beispiele für die Verwendung von Unity für die Abhängigkeitsinjektion. Ab Februar 2014 behandelt das Entwicklerhandbuch Unity 3.0, das im April 2013 veröffentlicht wurde.

Simon Tewsi
quelle
1

Ich beschreibe die meisten Beispiele für Abhängigkeitsinjektion in 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";
}

In DIAutoV2Controller.cs wird der automatische Injektionsmechanismus verwendet

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

In DIV2Controller.cs wird alles aus der Dependency Configuration Resolver-Klasse eingefügt

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

Konfigurieren des Abhängigkeitsauflösers

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
quelle
Dies ist aus mehreren Gründen keine besonders nützliche Antwort. Es ist ein unnötig komplexes Beispiel, das zu viel Code enthält, um eine einfache Erklärung des IOC zu bieten. Außerdem ist der Code an Stellen, an denen Sie ihn tatsächlich benötigen, nicht eindeutig dokumentiert.
Dan Atkinson