在本文中,咱们将通过用C#重构一个非常简单的代码示例来解释依赖注入和IoC容器。 

简介:

依赖注入和IoC乍一看可能相当简单,但它们非常容易学习和了解。

在本文中,咱们将通过在C#中重构一个非常简单的代码示例来解释依赖注入和IoC容器。

要求:

构建一个容许用户查看可用产品并按名称搜寻产品的应用程序。

第一次尝试:

咱们将从创立分层架构开始。应用分层架构有多个益处,但咱们不会在本文中列出它们,因为咱们关注的是依赖注入。

 上面是应用程序的类图:

首先,咱们将从创立一个Product类开始:

public class Product{    public Guid Id { get; set; }    public string Name { get; set; }    public string Description { get; set; }}

而后,咱们将创立数据拜访层:

public class ProductDAL{    private readonly List<Product> _products;    public ProductDAL()    {        _products = new List<Product>        {            new Product { Id = Guid.NewGuid(), Name= "iPhone 9",                           Description = "iPhone 9 mobile phone" },            new Product { Id = Guid.NewGuid(), Name= "iPhone X",                           Description = "iPhone X mobile phone" }        };    }    public IEnumerable<Product> GetProducts()    {        return _products;    }    public IEnumerable<Product> GetProducts(string name)    {        return _products            .Where(p => p.Name.Contains(name))            .ToList();    }}

而后,咱们将创立业务层:

public class ProductBL{    private readonly ProductDAL _productDAL;    public ProductBL()    {        _productDAL = new ProductDAL();    }    public IEnumerable<Product> GetProducts()    {        return _productDAL.GetProducts();    }    public IEnumerable<Product> GetProducts(string name)    {        return _productDAL.GetProducts(name);    }}

最初,咱们将创立UI:

class Program{    static void Main(string[] args)    {        ProductBL productBL = new ProductBL();        var products = productBL.GetProducts();        foreach (var product in products)        {            Console.WriteLine(product.Name);        }        Console.ReadKey();    }}

咱们曾经写在第一次尝试的代码是良好的工作成绩,但有几个问题:

1.咱们不能让三个不同的团队在每个层上工作。

2.业务层很难扩大,因为它依赖于数据拜访层的实现。

3.业务层很难保护,因为它依赖于数据拜访层的实现。

4.源代码很难测试。

第二次尝试:

高级别对象不应该依赖于低级别对象。两者都必须依赖于形象。那么抽象概念是什么呢?

形象是性能的定义。在咱们的例子中,业务层依赖于数据拜访层来检索图书。在C#中,咱们应用接口实现形象。接口示意性能的形象。

让咱们来创立形象。

上面是数据拜访层的形象:

public interface IProductDAL{    IEnumerable<Product> GetProducts();    IEnumerable<Product> GetProducts(string name);}

 咱们还须要更新数据拜访层:

public class ProductDAL : IProductDAL

咱们还须要更新业务层。实际上,咱们将更新业务层,使其依赖于数据拜访层的形象,而不是依赖于数据拜访层的实现:

public class ProductBL{    private readonly IProductDAL _productDAL;    public ProductBL()    {        _productDAL = new ProductDAL();    }    public IEnumerable<Product> GetProducts()    {        return _productDAL.GetProducts();    }    public IEnumerable<Product> GetProducts(string name)    {        return _productDAL.GetProducts(name);    }}

咱们还必须创立业务层的形象:

public interface IProductBL{    IEnumerable<Product> GetProducts();    IEnumerable<Product> GetProducts(string name);}

咱们也须要更新业务层:

public class ProductBL : IProductBL

最终咱们须要更新UI:

class Program{    static void Main(string[] args)    {        IProductBL productBL = new ProductBL();        var products = productBL.GetProducts();        foreach (var product in products)        {            Console.WriteLine(product.Name);        }        Console.ReadKey();    }}

咱们在第二次尝试中所做的代码是无效的,但咱们依然依赖于数据拜访层的具体实现:

public ProductBL(){    _productDAL = new ProductDAL();}

那么,如何解决呢?

这就是依赖注入模式发挥作用的中央。

最终尝试

到目前为止,咱们所做的工作都与依赖注入无关。

为了使处在较高级别的的业务层依赖于较低级别对象的性能,而没有具体的实现,必须由其他人创立类。其他人必须提供底层对象的具体实现,这就是咱们所说的依赖注入。它的字面意思是咱们将依赖对象注入到更高级别的对象中。实现依赖项注入的办法之一是应用构造函数进行依赖项注入。

让咱们更新业务层:

public class ProductBL : IProductBL{    private readonly IProductDAL _productDAL;    public ProductBL(IProductDAL productDAL)    {        _productDAL = productDAL;    }    public IEnumerable<Product> GetProducts()    {        return _productDAL.GetProducts();    }    public IEnumerable<Product> GetProducts(string name)    {        return _productDAL.GetProducts(name);    }}

 基础设施必须提供对实现的依赖:

class Program{    static void Main(string[] args)    {        IProductBL productBL = new ProductBL(new ProductDAL());        var products = productBL.GetProducts();        foreach (var product in products)        {            Console.WriteLine(product.Name);        }        Console.ReadKey();    }}

创立数据拜访层的管制与基础设施联合在一起。这也称为管制反转。咱们不是在业务层中创立数据拜访层的实例,而是在基础设施的中创立它。 Main办法将把实例注入到业务逻辑层。因而,咱们将低层对象的实例注入到高层对象的实例中。

这叫做依赖注入。

当初,如果咱们看一下代码,咱们只依赖于业务拜访层中数据拜访层的形象,而业务拜访层是应用的是数据拜访层实现的接口。因而,咱们遵循了更高层次对象和更低层次对象都依赖于形象的准则,形象是更高层次对象和更低层次对象之间的契约。

当初,咱们能够让不同的团队在不同的层上工作。咱们能够让一个团队解决数据拜访层,一个团队解决业务层,一个团队解决UI。

接下来就显示了可维护性和可扩展性的益处。例如,如果咱们想为SQL Server创立一个新的数据拜访层,咱们只需实现数据拜访层的形象并将实例注入基础设施中。

最初,源代码当初是可测试的了。因为咱们在任何中央都应用接口,所以咱们能够很容易地在较低的单元测试中提供另一个实现。这意味着较低的测试将更容易设置。

当初,让咱们测试业务层。

咱们将应用xUnit进行单元测试,应用Moq模仿数据拜访层。

上面是业务层的单元测试:

public class ProductBLTest{    private readonly List<Product> _products = new List<Product>    {        new Product { Id = Guid.NewGuid(), Name= "iPhone 9",                       Description = "iPhone 9 mobile phone" },        new Product { Id = Guid.NewGuid(), Name= "iPhone X",                       Description = "iPhone X mobile phone" }    };    private readonly ProductBL _productBL;    public ProductBLTest()    {        var mockProductDAL = new Mock<IProductDAL>();        mockProductDAL            .Setup(dal => dal.GetProducts())            .Returns(_products);        mockProductDAL            .Setup(dal => dal.GetProducts(It.IsAny<string>()))            .Returns<string>(name => _products.Where(p => p.Name.Contains(name)).ToList());        _productBL = new ProductBL(mockProductDAL.Object);    }    [Fact]    public void GetProductsTest()    {        var products = _productBL.GetProducts();        Assert.Equal(2, products.Count());    }    [Fact]    public void SearchProductsTest()    {        var products = _productBL.GetProducts("X");        Assert.Single(products);    }}

你能够看到,应用依赖项注入很容易设置单元测试。

IoC容器

容器只是帮忙实现依赖注入的货色。容器,通常实现三种不同的性能:

1.注册接口和具体实现之间的映射

2.创建对象并解析依赖关系

3.开释

让咱们实现一个简略的容器来注册映射并创建对象。

首先,咱们须要一个存储映射的数据结构。咱们将抉择Hashtable。该数据结构将存储映射。

首先,咱们将在容器的构造函数中初始化Hashtable。而后,咱们将创立一个RegisterTransient办法来注册映射。最初,咱们会创立一个创建对象的办法 Create :

public class Container{    private readonly Hashtable _registrations;    public Container()    {        _registrations = new Hashtable();    }    public void RegisterTransient<TInterface, TImplementation>()    {        _registrations.Add(typeof(TInterface), typeof(TImplementation));    }    public TInterface Create<TInterface>()    {        var typeOfImpl = (Type)_registrations[typeof(TInterface)];        if (typeOfImpl == null)        {            throw new ApplicationException($"Failed to resolve {typeof(TInterface).Name}");        }        return (TInterface)Activator.CreateInstance(typeOfImpl);    }}

最终,咱们会更新UI:

class Program{    static void Main(string[] args)    {        var container = new Container();        container.RegisterTransient<IProductDAL, ProductDAL>();        IProductBL productBL = new ProductBL(container.Create<IProductDAL>());        var products = productBL.GetProducts();        foreach (var product in products)        {            Console.WriteLine(product.Name);        }        Console.ReadKey();    }}

当初,让咱们在容器中实现Resolve办法。此办法将解决依赖关系。

Resolve办法如下:

public T Resolve<T>(){    var ctor = ((Type)_registrations[typeof(T)]).GetConstructors()[0];    var dep = ctor.GetParameters()[0].ParameterType;    var mi = typeof(Container).GetMethod("Create");    var gm = mi.MakeGenericMethod(dep);    return (T)ctor.Invoke(new object[] { gm.Invoke(this, null) });}

而后咱们能够在UI中应用如下Resolve办法:

class Program{    static void Main(string[] args)    {        var container = new Container();        container.RegisterTransient<IProductDAL, ProductDAL>();        container.RegisterTransient<IProductBL, ProductBL>();        var productBL = container.Resolve<IProductBL>();        var products = productBL.GetProducts();        foreach (var product in products)        {            Console.WriteLine(product.Name);        }        Console.ReadKey();    }}

在下面的源代码中,容器应用container.Resolve<IProductBL>()办法创立ProductBL类的一个对象。ProductBL类是IProductDAL的一个依赖项。因而,container.Resolve<IProductBL>() 通过主动创立并在其中注入一个ProductDAL对象返回ProductBL类的一个对象。这一切都在幕后进行。创立和注入ProductDAL对象是因为咱们用IProductDAL注册了ProductDAL类型。

这是一个非常简单和根本的IoC容器,它向你展现了IoC容器背地的内容。就是这样。我心愿你喜爱浏览这篇文章。

 欢送关注我的公众号,如果你有喜爱的外文技术文章,能够通过公众号留言举荐给我。