zoukankan      html  css  js  c++  java
  • C#中的依赖注入和IoC容器

    在本文中,我们将通过用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容器背后的内容。就是这样。我希望你喜欢阅读这篇文章。

     欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。

    原文链接:https://www.codeproject.com/Articles/5274732/Dependency-Injection-and-IoC-Containers-in-Csharp

  • 相关阅读:
    20155217 实验四 Android程序设计
    20155217 第十二周课堂测试
    20155217 《Java程序设计》第三次实验报告
    20155217 2016-2017-2 《Java程序设计》第10周学习总结
    20155217 实验二 Java面向对象程序设计 实验报告
    20155217 2016-2017-2 《Java程序设计》第9周学习总结
    20155217 2016-2017-2 《Java程序设计》第8周学习总结
    实验一 Java开发环境的熟悉(Linux+Eclipse)
    Spring阶段性学习总结(一)实现一个简单的HellowWorld
    MySql数据库再学习——使用强化版的自定义连接池连接数据库
  • 原文地址:https://www.cnblogs.com/hhhnicvscs/p/14204806.html
Copyright © 2011-2022 走看看