Может кто-нибудь объяснить Microsoft Unity?

157

Я читал статьи на MSDN о Unity (внедрение зависимостей, инверсия управления), но думаю, мне нужно объяснить это в простых терминах (или простых примерах). Я знаком с шаблоном MVPC (мы используем его здесь), но я пока не могу по-настоящему понять эту вещь Unity, и я думаю, что это следующий шаг в разработке нашего приложения.

Райан Эбботт
источник
12
Мне нравится, что у него такое же имя, как и у «Unity», поэтому, когда я ищу материал для Unity Game Engine, я вижу эту старую технологию, вздох. Все хорошие названия групп взяты, я думаю.
Том Шульц
2
@ Том-Шульц Старый техник? nuget.org/packages/Unity - последнее обновление 5 дней назад.
Роджер Уиллкокс

Ответы:

174

Unity - это просто «контейнер» IoC. Google StructureMap и попробуйте вместо этого. Я думаю, что немного проще, когда я начинаю с нововведениями в IoC.

По сути, если вы понимаете IoC, то понимаете, что то, что вы делаете, инвертирует контроль, когда объект создается.

Без IoC:

public class MyClass
{
   IMyService _myService; 

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

С контейнером IoC:

public class MyClass
{
   IMyService _myService; 

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

Без IoC ваш класс, опирающийся на IMyService, должен обновить конкретную версию службы для использования. И это плохо по ряду причин (вы связали свой класс с конкретной конкретной версией IMyService, вы не можете легко выполнить модульное тестирование, вы не можете легко изменить его и т. Д.)

С контейнером IoC вы «конфигурируете» контейнер для разрешения этих зависимостей за вас. Таким образом, при использовании схемы внедрения на основе конструктора вы просто передаете интерфейс зависимости IMyService в конструктор. Когда вы создаете MyClass с вашим контейнером, ваш контейнер разрешит зависимость IMyService для вас.

Используя StructureMap, настройка контейнера выглядит следующим образом:

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

Итак, вы сказали контейнеру: «Когда кто-то запрашивает IMyService, дайте ему копию SomeConcreteService». И вы также указали, что когда кто-то запрашивает MyClass, он получает конкретный MyClass.

Это все, что делает контейнер IoC. Они могут сделать больше, но в этом суть - они разрешают зависимости для вас, поэтому вам не нужно (и вам не нужно использовать ключевое слово «new» во всем коде).

Последний шаг: когда вы создаете свой MyClass, вы должны сделать это:

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

Надеюсь, это поможет. Не стесняйтесь, напишите мне.

Крис Холмс
источник
2
Значит, это как фабрика? Если я правильно понимаю, не будете ли вы использовать <IMyClass> вместо <MyClass> в последнем примере? так было бы var myClass = ObjectFactory.GetInstance <IMyClass> ()? Спасибо за вашу помощь, это хорошее начало для меня!
Райан Эбботт
3
В некотором смысле, это как завод, да. Мастерская фабрика для вашего применения. Но его можно настроить так, чтобы он возвращал множество разных типов, включая синглтоны. Что касается интерфейса с MyClass - если это бизнес-объект, я бы не стал извлекать интерфейс. Для всего остального я бы вообще.
Крис Холмс
Что делать, если вы только вызвали ObjectFactory.GetInstance <MyClass> (); а вы не настраивали SomeConcreteClass? Вы получите и ошибку в этом случае?
RayLoveless
1
@Ray: это зависит от контейнера. Некоторые контейнеры написаны так, что по умолчанию они используют соглашение об именах, например, если класс называется MyClass, а интерфейс называется IMyInterface, контейнер автоматически настроит этот класс для этого интерфейса. Так что в этом случае, если вы не сконфигурируете его вручную, «соглашение» контейнера по умолчанию все равно его подхватит. Однако, если ваш класс и интерфейс не соответствуют соглашению и вы не настраиваете контейнер для этого класса, тогда да, вы получите ошибку во время выполнения.
Крис Холмс
1
@saravanan Я думаю, что StructureMap сейчас делает соглашение на основе имен. Я не уверен; мы не использовали его долгое время (я написал специальный для нашего бизнеса; он использует одноименное соглашение для интерфейсов и классов).
Крис Холмс
39

Я только что посмотрел 30-минутный скринкаст IoC по инъекции зависимостей от Unity Дэвида Хейдена и почувствовал, что это хорошее объяснение примерами. Вот фрагмент из заметок шоу:

Скринкаст демонстрирует несколько распространенных способов использования Unity IoC, таких как:

  • Создание типов не в контейнере
  • Регистрация и разрешение TypeMappings
  • Регистрация и разрешение именованных типов сопоставлений
  • Одиночные, LifetimeManager и ContainerControlledLifetimeManager
  • Регистрация существующих экземпляров
  • Внедрение зависимостей в существующие экземпляры
  • Заполнение UnityContainer через App.config / Web.config
  • Задание зависимостей через API инъекций, в отличие от атрибутов зависимостей
  • Использование вложенных (Parent-Child) контейнеров
Кевин Хакансон
источник
32

Unity - это библиотека, как и многие другие, которая позволяет вам получить экземпляр запрошенного типа, не создавая его самостоятельно. Так дано.

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

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

Вы бы использовали библиотеку, например Unity, чтобы зарегистрировать Calculator, который будет возвращен, когда запрашивается тип ICalculator, также известный как IoC (инверсия управления) (этот пример теоретический, а не технически правильный).

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

Итак, теперь, когда вам нужен экземпляр ICalculator, вы просто ...

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

Библиотеки IoC обычно можно настроить для хранения одиночного или нового экземпляра при каждом разрешении типа.

Теперь предположим, что у вас есть класс, который зависит от наличия ICalculator, который вы могли бы иметь ...

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

    private ICalculator _calc;
}

И вы можете настроить библиотеку для вставки объекта в конструктор при его создании.

Таким образом, DI или Dependency Injection означает внедрение любого объекта, который может потребоваться другому.

Чад Моран
источник
должен быть ICalculator calc = IoCLibrary.Resolve <ICalculator> ();
Шухрат Раимов
10

Единство - это МОК. Смысл IoC состоит в том, чтобы абстрагировать зависимости между типами вне самих типов. Это имеет пару преимуществ. Прежде всего, это делается централизованно, что означает, что вам не нужно менять много кода при изменении зависимостей (что может быть в случае модульных тестов).

Кроме того, если подключение выполняется с использованием данных конфигурации, а не кода, вы можете фактически перезаписать зависимости после развертывания и, таким образом, изменить поведение приложения без изменения кода.

Брайан Расмуссен
источник
5

В MSDN есть Руководство разработчика по внедрению зависимостей с использованием Unity, которое может оказаться полезным.

Руководство разработчика начинается с основ того, что такое внедрение зависимостей, и продолжается примерами использования Unity для внедрения зависимостей. По состоянию на февраль 2014 года Руководство разработчика охватывает Unity 3.0, выпущенное в апреле 2013 года.

Саймон Тьюси
источник
1

Я рассматриваю большинство примеров внедрения зависимостей в 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";
}

В DIAutoV2Controller.cs используется механизм 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;
    }
}

В DIV2Controller.cs все будет введено из класса Resolver Configuration Dependency 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;
    }
}

Конфигурирование решателя зависимостей

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")));
}
Нароттам Гоял
источник
Это не особенно полезный ответ по ряду причин. Это излишне сложный пример, в котором слишком много кода, чтобы быть полезным для простого объяснения МОК. Кроме того, код не документирован четко в тех местах, где он вам действительно нужен.
Дэн Аткинсон