有人能解释一下微软Unity是什么吗?

164

我一直在阅读有关Unity(依赖注入,控制反转)的MSDN文章,但我觉得我需要简单地解释它(或者简单的例子)。我熟悉MVPC模式(我们在这里使用它),但我仍然无法真正理解这个Unity,而且我认为这是我们应用程序设计的下一步。


15
我喜欢这个和“Unity”叫一样的名字,但是当我搜索Unity游戏引擎的东西时会看到这个老技术,唉。所有好的乐队名字都被用了,我猜。 - Tom Schulz
3
@tom-schulz 旧技术? https://www.nuget.org/packages/Unity/ - 最近5天更新。 - Roger Willcocks
6个回答

185

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”关键字(也不必在代码中随处使用“new”关键字)。

最后一步:当你创建你的MyClass时,你会这样做:

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

3
那就像一个工厂,我猜?如果我理解正确的话,在最后一个例子中,你不是应该使用<IMyClass>而不是<MyClass>吗?这样它就会变成var myClass = ObjectFactory.GetInstance<IMyClass>()了吧?感谢您的帮助,这对我来说是一个很好的开始! - Ryan Abbott
3
某种程度上,它就像一个工厂。是你应用程序的主要工厂,但可以配置返回许多不同类型的对象,包括单例模式。至于MyClass的接口——如果它是业务对象,我不会提取一个接口。而对于其他所有情况,通常我都会提取接口。 - Chris Holmes
如果你只调用了 ObjectFactory.GetInstance<MyClass>(); 而没有配置 SomeConcreteClass,那么会出现错误吗? - RayLoveless
1
@Ray:这取决于容器。有些容器默认使用命名约定,例如如果一个类被命名为MyClass,接口被命名为IMyInterface,那么容器会自动为该类配置该接口。因此,在这种情况下,如果您没有手动配置它,容器的默认“约定”仍然会将其捕获。但是,如果您的类和接口不遵循约定并且您没有为该类配置容器,则在运行时会出现错误。 - Chris Holmes
@chris:你知道有没有一个基于命名约定的类型解析的 .net 开源容器吗? - Saravanan
1
@saravanan 我认为StructureMap现在采用基于名称的约定。我不确定;我们已经很久没有使用它了(我为我们的业务编写了一个自定义的,它使用接口和类的同名约定)。 - Chris Holmes

39

我刚刚观看了David Hayden的30分钟Unity依赖注入IoC视频,并且感觉这一段视频通过示例展示讲解得非常好。以下是节目笔记中的一部分:

该视频展示了Unity IoC的几种常见用法,包括:

  • 创建不在容器中的类型
  • 注册和解析类型映射
  • 注册和解析命名类型映射
  • 单例、生命周期管理器和ContainerControlledLifetimeManager
  • 注册现有实例
  • 将依赖项注入到现有实例中
  • 通过App.config / Web.config填充UnityContainer
  • 使用嵌套(父子)容器

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)是指向另一个可能需要的任何对象注入对象。


应该是ICalculator calc = IoCLibrary.Resolve<ICalculator>(); - Shukhrat Raimov

10

Unity 是一个 IoC(控制反转)容器。IoC 的目的是将类型之间的依赖关系从类型本身抽象出来进行连接。这有几个优点。首先,它是在中央处理的,这意味着当依赖关系发生更改时(例如单元测试),您不必更改大量代码。

此外,如果使用配置数据而不是代码进行连接,则可以在部署后重新连接依赖项,从而改变应用程序的行为而无需更改代码。


它有一些优点和几个缺点。挺好的。我想感谢你们时代的所有开发人员,留下这些东西让年轻一代去处理 :) 非常有帮助。 - gabriel.hayes

5

MSDN有一份使用Unity进行依赖注入的开发人员指南,可能会很有用。

该开发人员指南从依赖注入的基础知识开始,继续介绍如何使用Unity进行依赖注入的示例。截至2014年2月,该开发人员指南涵盖了于2013年4月发布的Unity 3.0版本。


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中使用了自动注入机制。
[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中,所有内容都将从依赖配置解析器类中注入。
[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")));
}

这并不是一个特别有用的答案,原因有很多。它是一个不必要复杂的例子,代码太多了,无法提供关于IOC的简单解释。此外,在你实际需要的地方,代码没有清晰的文档说明。 - Dan Atkinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接