Qualcuno può spiegare Microsoft Unity?


157

Ho letto gli articoli su MSDN su Unity (Dependency Injection, Inversion of Control), ma penso di averne bisogno spiegato in termini semplici (o semplici esempi). Ho familiarità con il modello MVPC (lo usiamo qui), ma non riesco ancora a capire davvero questa cosa di Unity e penso che sia il prossimo passo nella progettazione delle nostre applicazioni.


12
Adoro il modo in cui questo ha lo stesso nome di "Unity", quindi quando cerco cose su Unity Game Engine vedo questa vecchia tecnologia, sospiro. Immagino che vengano presi tutti i bei nomi di band.
Tom Schulz,

2
@ tom-schulz Vecchia tecnologia? nuget.org/packages/Unity - ultimo aggiornamento 5 giorni fa.
Roger Willcocks,

Risposte:


174

Unity è solo un "contenitore" IoC. Google StructureMap e provalo invece. Un po 'più facile da capire, penso, quando le cose IoC sono nuove per te.

Fondamentalmente, se capisci l'IoC, capisci che quello che stai facendo è invertire il controllo per quando viene creato un oggetto.

Senza IoC:

public class MyClass
{
   IMyService _myService; 

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

Con contenitore IoC:

public class MyClass
{
   IMyService _myService; 

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

Senza IoC, la tua classe che si affida a IMyService deve rinnovare una versione concreta del servizio da utilizzare. E questo è negativo per una serie di motivi (hai abbinato la tua classe a una specifica versione concreta di IMyService, non puoi testarla facilmente, non puoi cambiarla facilmente, ecc.)

Con un contenitore IoC "configuri" il contenitore per risolvere quelle dipendenze per te. Quindi, con uno schema di iniezione basato sul costruttore, è sufficiente passare l'interfaccia alla dipendenza IMyService nel costruttore. Quando crei MyClass con il tuo contenitore, il tuo contenitore risolverà la dipendenza da IMyService per te.

Utilizzando StructureMap, la configurazione del contenitore è simile alla seguente:

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

Quindi quello che hai fatto è stato detto al contenitore: "Quando qualcuno richiede IMyService, dai loro una copia di SomeConcreteService." E hai anche specificato che quando qualcuno chiede una MyClass, ottiene una MyClass concreta.

Questo è tutto ciò che fa davvero un contenitore IoC. Possono fare di più, ma questo è il punto di forza: risolvono le dipendenze per te, quindi non devi (e non devi usare la "nuova" parola chiave in tutto il tuo codice).

Passaggio finale: quando crei MyClass, esegui questa operazione:

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

Spero che aiuti. Sentiti libero di mandarmi una e-mail.


2
Quindi è come una fabbrica, suppongo? Se lo sto seguendo correttamente, non useresti <IMyClass> invece di <MyClass> nell'esempio finale? quindi sarebbe var myClass = ObjectFactory.GetInstance <IMyClass> ()? Grazie per il tuo aiuto, questo è un buon inizio per me!
Ryan Abbott

3
In un certo senso, è come una fabbrica, sì. Una fabbrica principale per la tua applicazione. Ma può essere configurato per restituire molti tipi diversi, inclusi i singoli. Per quanto riguarda l'interfaccia di MyClass: se si tratta di un oggetto business, non estrarrei un'interfaccia. Per tutto il resto, generalmente lo farei.
Chris Holmes,

cosa succede se si chiama solo ObjectFactory.GetInstance <MyClass> (); e non hai configurato SomeConcreteClass? Vorresti ricevere un errore in quel caso?
RayLoveless

1
@Ray: dipende dal contenitore. Alcuni contenitori sono scritti in modo che, per impostazione predefinita, utilizzino una convenzione di denominazione, in modo tale che se una classe è denominata MyClass e l'interfaccia è denominata IMyInterface, il contenitore configurerà automaticamente quella classe per tale interfaccia. Quindi, in tal caso, se non lo si configura manualmente, la "convenzione" predefinita del contenitore lo prende comunque. Tuttavia, se la classe e l'interfaccia non seguono la convenzione e non si configura il contenitore per quella classe, sì, si ottiene un errore in fase di esecuzione.
Chris Holmes,

1
@saravanan Penso che StructureMap faccia una convenzione basata sul nome adesso. Non ne sono certo; non lo usiamo da molto tempo (ne ho scritto uno personalizzato per la nostra attività; utilizza convenzioni omonime per interfacce e classi).
Chris Holmes,

39

Ho appena visto lo screencast IoC di Inity Dependency Injection di 30 minuti di David Hayden e ho pensato che fosse una buona spiegazione con esempi. Ecco un frammento delle note dello spettacolo:

Lo screencast mostra diversi usi comuni di Unity IoC, come:

  • Creazione di tipi non in contenitore
  • Registrazione e risoluzione di TypeMappings
  • Registrazione e risoluzione di TypeMappings denominati
  • Singletons, LifetimeManager e ContainerControlledLifetimeManager
  • Registrazione delle istanze esistenti
  • Iniezione di dipendenze in istanze esistenti
  • Popolazione di UnityContainer tramite App.config / Web.config
  • Specifica delle dipendenze tramite API di iniezione anziché Attributi di dipendenza
  • Utilizzo dei contenitori nidificati (padre-figlio)

32

Unity è una libreria come molte altre che ti consente di ottenere un'istanza di un tipo richiesto senza doverla creare tu stesso. Così dato.

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

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

Si userebbe una libreria come Unity per registrare la calcolatrice da restituire quando viene richiesto il tipo ICalculator aka IoC (Inversion of Control) (questo esempio è teorico, non tecnicamente corretto).

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

Quindi ora quando vuoi un'istanza di un ICalculator devi solo ...

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

Le librerie IoC possono in genere essere configurate per contenere un singleton o creare una nuova istanza ogni volta che si risolve un tipo.

Ora diciamo che hai una classe che si basa su un ICalculator per essere presente che potresti avere ..

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

    private ICalculator _calc;
}

E puoi impostare la libreria per iniettare un oggetto nel costruttore quando viene creato.

Quindi DI o Dependency Injection significa iniettare qualsiasi oggetto che un altro potrebbe richiedere.


dovrebbe essere ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Shukhrat Raimov,


10

Unity è un IoC. Il punto di IoC è quello di astrarre il cablaggio delle dipendenze tra tipi al di fuori dei tipi stessi. Questo ha un paio di vantaggi. Prima di tutto, viene eseguito centralmente, il che significa che non è necessario modificare molto codice quando cambiano le dipendenze (che può essere il caso dei test unitari).

Inoltre, se il cablaggio viene eseguito utilizzando i dati di configurazione anziché il codice, è possibile ricollegare le dipendenze dopo la distribuzione e quindi modificare il comportamento dell'applicazione senza modificare il codice.



1

Sto coprendo la maggior parte degli esempi di Iniezione delle dipendenze nell'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";
}

In DIAutoV2Controller.cs viene utilizzato il meccanismo di iniezione automatica

[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 tutto verrà iniettato dalla classe Resolver di configurazione delle dipendenze

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

Configurazione del resolver dipendenze

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")));
}

Questa non è una risposta particolarmente utile per una serie di motivi. È un esempio inutilmente complesso che ha troppo codice per essere utile nell'offrire una semplice spiegazione del CIO. Oltre a ciò, il codice non è documentato chiaramente nei luoghi in cui ne avresti effettivamente bisogno.
Dan Atkinson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.