ASP.NET Core 3.1系列(1)——依赖注入和控制反转

32 篇文章 43 订阅

1、前言

很长时间没写博客了,今年的主要任务就是把原先.NET Framework平台的项目迁移到.NET Core平台,在研究了一阵子后,我发现.NET Core实在是让人爱不释手,所以还没入坑的小伙伴可要抓紧了,争取早日体验一把真香定律。言归正传,由于现在的项目采用的都是前后端分离模式,因此本系列博客主要介绍ASP.NET Core中的WebAPI开发模式,开发环境为Visual Studio 2019,数据库选择SQL Server 2019,缓存数据库选择Redis。本文主要介绍依赖注入和控制反转的相关概念,下面就让我们开始吧。

2、依赖于具体类产生的问题

首先明确一点:什么情况下会产生依赖关系?其实很简单,当一个类中包含另一个类的实例的时候就产生了依赖关系。那么这种依赖关系会给应用程序带来什么问题?先看下面的例子:

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            SqlServer db = new SqlServer();
            DbManager manager = new DbManager(db);
            manager.Read();
        }
    }

    public class SqlServer
    {
        public void Read()
        {
            Console.WriteLine("SqlServer读取数据");
        }
    }

    public class DbManager
    {
        private SqlServer db;

        public DbManager(SqlServer db)
        {
            this.db = db;
        }

        public void Read()
        {
            db.Read();
        }
    }
}

只要对面向对象的开发方式略微熟悉一点,你就应该能看出上面代码的问题:DbManager类中包含了一个SqlServer类的实例,如果以后需要改成MySql读取数据,我们将不得不修改DbManager的代码,很显然这违反了开闭原则。因此,过多依赖于具体类将导致应用程序的可扩展性大大降低。

3、依赖于抽象接口

既然依赖于具体类会导致程序的可扩展性降低,那么我们自然而然会想到使用接口。现在对上面的例子稍作修改,增加一个IDb接口,代码如下:

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            IDb db = new MySql();
            DbManager manager = new DbManager(db);
            manager.Read();
        }
    }

    public interface IDb
    {
        void Read();
    }

    public class SqlServer : IDb
    {
        public void Read()
        {
            Console.WriteLine("SqlServer读取数据");
        }
    }

    public class MySql : IDb
    {
        public void Read()
        {
            Console.WriteLine("MySql读取数据");
        }
    }

    public class DbManager
    {
        private IDb db;

        public DbManager(IDb db)
        {
            this.db = db;
        }

        public void Read()
        {
            db.Read();
        }
    }
}

对于DbManager类,现在它不再依赖于某个具体的数据库访问类,而是依赖于一个抽象接口,这样做的好处也是显而易见的,如果以后需要改成使用Oracle读取数据,DbManager不需要做任何修改,只需要在Main方法中传入一个Oracle类实例即可。

4、依赖注入

现在我们已经解决了依赖关系,下面就得考虑第二个问题:什么是注入?其实注入就好比去医院打针,医生不可能在你身上随便找个地方就把针扎进去,肯定得选择合适的部位。依赖注入也是一样,对于一个类来说,合适的注入点一般就是构造函数和成员方法。

4.1、构造注入

构造注入 (Constructor Injection) 是指在客户类中设置一个服务类接口类型的数据成员,并以构造函数为注入点,这个构造函数接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。由于构造函数注入只能在实例化客户类时注入一次,因此程序运行期间是没法改变一个客户类对象内的服务类实例的。其代码如下所示:

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            DbManager manager = new DbManager(new SqlServer());
            manager.Read();
        }
    }

    public interface IDb
    {
        void Read();
    }

    public class SqlServer : IDb
    {
        public void Read()
        {
            Console.WriteLine("SqlServer读取数据");
        }
    }

    public class DbManager
    {
        private IDb db;

        public DbManager(IDb db)
        {
            this.db = db;
        }

        public void Read()
        {
            db.Read();
        }
    }
}

4.2、Setter注入

Setter注入 (Setter Injection) 是指在客户类中设置一个服务类接口类型的数据成员,并设置一个Set方法作为注入点,这个Set方法接受一个具体的服务类实例为参数,并将它赋给服务类接口类型的数据成员。该方法的好处就是可以在程序运行期间动态修改客户端类中的服务对象,其代码如下所示:

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            DbManager manager = new DbManager();
            manager.SetDb(new SqlServer());
            manager.Read();
        }
    }

    public interface IDb
    {
        void Read();
    }

    public class SqlServer : IDb
    {
        public void Read()
        {
            Console.WriteLine("SqlServer读取数据");
        }
    }

    public class DbManager
    {
        private IDb db;

        public void SetDb(IDb db)
        {
            this.db = db;
        }

        public void Read()
        {
            db.Read();
        }
    }
}

4.3、接口注入

接口注入是将包括抽象类型注入的入口以方法的形式定义在一个接口里,如果客户类型需要实现这个注入过程,则实现这个接口,客户类型自己考虑如何把抽象类型“引入”内部。实际上接口注入有很强的侵入性,因为它显式地定义了一个新的接口并要求客户类型实现它。与前两种注入方式相比,接口注入并不常用,其代码如下所示:

using System;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            DbManager manager = new DbManager();
            manager.Db = new SqlServer();
            manager.Read();
        }
    }

    public interface IDb
    {
        void Read();
    }

    public class SqlServer : IDb
    {
        public void Read()
        {
            Console.WriteLine("SqlServer读取数据");
        }
    }

    public interface IDbManager
    {
        IDb Db { get; set; }
    }

    public class DbManager : IDbManager
    {
        public IDb Db { get; set; }

        public void Read()
        {
            Db.Read();
        }
    }
}

5、控制反转

提到依赖注入,就不能不提控制反转。控制反转 (Inversion of Control) ,简称IoC,是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。

5.1、谁控制谁?什么被反转了?

在传统的程序开发中,开发人员往往会手动去new一个对象,换言之:对象的创建过程由开发人员控制。而在控制反转的原则下,系统中会存在一个专门的IoC容器,由IoC容器负责对象的创建。因此,创建对象的主动权从开发人员手中被移交到了容器手中,由容器控制对象的创建过程,这就是控制反转的含义。

5.2、编写一个简单的IoC容器

下面就来编写一个简单的IoC容器,主要分为IoCControllerServiceRepositoryModel五部分。依赖关系很简单,Controller—>Service—>Repository,最后在IoC中注册并调用,代码如下。

5.2.1、Model
namespace App.Model
{
    public class UserInfo
    {
        public string UserName { get; set; }
        public string Password { get; set; }
    }
}
5.2.2、Repository
using System.Collections.Generic;
using App.Model;

namespace App.Repository
{
    public interface IUserInfoRepository
    {
        IEnumerable<UserInfo> GetUserInfoList();
    }
}
using System.Collections.Generic;
using App.Model;

namespace App.Repository
{
    public class UserInfoRepository : IUserInfoRepository
    {
        public IEnumerable<UserInfo> GetUserInfoList()
        {
            List<UserInfo> list = new List<UserInfo>();
            list.Add(new UserInfo { UserName = "super", Password = "12345" });
            list.Add(new UserInfo { UserName = "admin", Password = "12345" });
            return list;
        }
    }
}
5.2.3、Service
using System.Collections.Generic;
using App.Model;

namespace App.Service
{
    public interface IUserInfoService
    {
        IEnumerable<UserInfo> GetUserInfoList();
    }
}
using System;
using System.Collections.Generic;
using App.Model;
using App.Repository;

namespace App.Service
{
    public class UserInfoService : IUserInfoService
    {
        private readonly IUserInfoRepository repository;

        public UserInfoService(IUserInfoRepository repository)
        {
            this.repository = repository;
        }

        public IEnumerable<UserInfo> GetUserInfoList()
        {
            return repository.GetUserInfoList();
        }
    }
}
5.2.4、Controller
using System.Collections.Generic;
using App.Model;

namespace App.Controller
{
    public interface IUserInfoController
    {
        IEnumerable<UserInfo> GetUserInfoList();
    }
}
using System.Collections.Generic;
using App.Model;
using App.Service;

namespace App.Controller
{
    public class UserInfoController : IUserInfoController
    {
        private readonly IUserInfoService service;

        public UserInfoController(IUserInfoService service)
        {
            this.service = service;
        }

        public IEnumerable<UserInfo> GetUserInfoList()
        {
            return service.GetUserInfoList();
        }
    }
}
5.2.5、IoC
using System;
using System.Collections.Generic;
using System.Reflection;

namespace App.IoC
{
    public class SimpleIocContainer
    {
        private readonly Dictionary<string, Type> container;

        /// <summary>
        /// 构造函数
        /// </summary>
        public SimpleIocContainer()
        {
            container = new Dictionary<string, Type>();
        }

        /// <summary>
        /// 注册类型
        /// </summary>
        /// <typeparam name="TSuper">超类</typeparam>
        /// <typeparam name="TSub">子类</typeparam>
        public void RegisterType<TSuper, TSub>()
            where TSuper : class
            where TSub : class
        {
            string fullName = typeof(TSuper).FullName;
            if (!container.ContainsKey(fullName))
            {
                container.Add(fullName, typeof(TSub));
            }
        }

        /// <summary>
        /// 获取需要的类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T Resolve<T>() where T : class
        {
            string fullName = typeof(T).FullName;
            if (!container.ContainsKey(fullName))
            {
                throw new Exception("Error");
            }
            return (T)CreateObject(container[fullName]);
        }

        /// <summary>
        /// 递归创建对象
        /// </summary>
        /// <param name="type">子类类型</param>
        /// <returns>object</returns>
        private object CreateObject(Type type)
        {
            ConstructorInfo[] constructors = type.GetConstructors();
            ConstructorInfo constructor = GetConstructor(constructors);
            List<object> list = new List<object>();
            foreach (ParameterInfo parameter in constructor.GetParameters())
            {
                Type paraType = parameter.ParameterType;
                if (!container.ContainsKey(paraType.FullName))
                {
                    throw new Exception("Error");
                }
                list.Add(CreateObject(container[paraType.FullName]));
            }
            return Activator.CreateInstance(type, list.ToArray());
        }

        /// <summary>
        /// 获取构造函数
        /// </summary>
        /// <param name="constructors">构造函数集合</param>
        /// <returns>构造函数</returns>
        private ConstructorInfo GetConstructor(ConstructorInfo[] constructors)
        {
            // 构造函数个数为0,则抛出异常
            if (constructors.Length == 0)
            {
                throw new Exception("Error");
            }

            // 构造函数个数为1,则直接返回
            if (constructors.Length == 1)
            {
                return constructors[0];
            }

            // 获取形参个数最多的构造函数
            ConstructorInfo constructor = constructors[0];
            foreach (var item in constructors)
            {
                if (item.GetParameters().Length > constructor.GetParameters().Length)
                {
                    constructor = item;
                }
            }
            return constructor;
        }
    }
}
5.2.6、主程序调用
using System;
using App.Controller;
using App.IoC;
using App.Repository;
using App.Service;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建IoC容器,注册接口和类
            var container = new SimpleIocContainer();
            container.RegisterType<IUserInfoRepository, UserInfoRepository>();
            container.RegisterType<IUserInfoService, UserInfoService>();
            container.RegisterType<IUserInfoController, UserInfoController>();

            // 利用IoC容器自动创建对象
            var controller = container.Resolve<IUserInfoController>();

            // 输出结果
            var list = controller.GetUserInfoList();
            foreach (var item in list)
            {
                Console.WriteLine(item.UserName + "," + item.Password);
            }
            Console.ReadKey(true);
        }
    }
}

运行结果如下所示:

super,12345
admin,12345

在上面的代码中,我们并没有手动去创建RepositoryServiceController这些类的实例,而是通过在IoC容器中注册它们的依赖关系,然后通过Resolve方法自动创建对应的实例。这也就是上面所说的:把创建对象的主动权交给容器,让容器帮助我们自动创建对象。上面给出的只是一个最简单的IoC容器实现方法,有兴趣的同志可以参考AutofacUnity等第三方框架进行拓展。

6、结语

本文主要介绍了依赖注入和控制反转的相关概念,依赖注入的核心思想就是依赖于抽象接口而非某个具体的类,然后寻找合适的注入点注入对象,这会使系统在后期会更具灵活性和扩展性。而控制反转则是将创建对象的主动权交到IoC容器手中,利用IoC容器管理存在依赖关系的若干类,当我们需要使用某个对象时,直接到IoC容器中去获取即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值