控制反转和依赖注入模式

一、控制反转和依赖注入两者搭配能像反射工厂那样解决程序集之间的耦合问题,下面将从Asp.Net经典的三层模式多方位的讲解控制反转和依赖注入模式,是如何帮我们进行程序集之间的解耦的。

上图是最基本的三层框架,具体的流程如下:

1、表现层调用业务层的方法

2、业务层调用数据层的方法,并对数据层返回的基础数据进行加工返回给业务层

3、数据层与数据库进行数据交互,并将数据传递给业务层

同时,如果让你给这三个层进行一个层次的划分,你会怎么划?

我觉得表现层在整个框架中是最高层次的,因为表现层是最抽象,其次是业务层,最后是数据层,数据层可以说是整个系统的底层模块,他管理着系统最基础的数据。

下面查看一个添加一个用户的实例,观察业务层和数据层是如何交互的

UserBll.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dal;

namespace Bll
{
    public class UserBll
    {
        UserDal dal = new UserDal();
        public int AddUser()
        {
            return dal.AddUser();
        }
    }
}

ok,这段代码一点问题没有,业务层调用了数据层的增加用户的方法,完成了用户的添加,并将添加成功与否的结果返回给调用它的对象,但是问题就住在这!!!什么问题呢?

注意关键字new,这个new导致了业务层实例与数据层实例强耦合在了一起。

1、上面的实例代码违反了依赖倒置原则,何为依赖倒置原则,如下所示

依赖倒置原则:

a、高层次的模块不应该依赖于低层次的模块,他们应该依赖于抽象

b、抽象不应该依赖于具体,具体应该依赖抽象

上面我以及分析出了,业务层高于数据层,所以业务层不应该依赖于数据层,而应该依赖于数据层的抽象。

2、如果你不明白依赖倒置原则,你也可以这样理解,我们知道数据库程序不止一种,那相应的我们的数据层也不应该只有一种,打个比方,假设现在的数据层是用SqlServer编写的,如果某一天你的Boss告诉你说,这个项目要进行升级,数据库换成Oralce的,这个时候,你怎么办,你这里的业务层已经和SqlServer数据层强耦合在了一起,总不可能将这个项目反编译,然后在修改里面的源码吧,这显然是不可能的

现在我们知道了问题,所以必须让业务层依赖一个抽象数据层,而不是一个具体的数据层,接下来新建一个抽象数据层

IUserDal代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IDal
{
    public interface IUserDal
    {
        int AddUser();
    }
}

UserBll.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dal;
using IDal;

namespace Bll
{
    public class UserBll
    {
        IUserDal dal = new UserDal();
        public int AddUser()
        {
            return dal.AddUser();
        }
    }
}

ok,现在业务层依赖的不再是特定的数据层实例,此时的业务层依赖于抽象,只要是实现IUserDal的数据层实例都能被业务层所接纳。so,现在在看Dal中

UserDal.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IDal;

namespace Dal
{
    public class UserDal : IUserDal
    {
        public int AddUser()
        {
            //和SqlServer数据库交互的方法
            return 1;
        }
    }
}

如果,这个时候你的Boss说了换成Oracle,那就很简单了,新建一个OracleDal的类库

UserDal.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IDal;

namespace OracleDal
{
    public class UserDal : IUserDal
    {
        public int AddUser()
        {
            // //和Oracle数据库交互的方法
            return 1;
        }
    }
}

下面再看业务层UserBll.cs的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dal;
using IDal;

namespace Bll
{
    public class UserBll
    {
        IUserDal dal = new OracleDal.UserDal();
        //IUserDal dal = new Dal.UserDal(); 也可以调用SqlServer数据层中的UserDal类
        public int AddUser()
        {
            return dal.AddUser();
        }
    }
}

现在业务层从逻辑上将,就能随笔的切换数据层,当然从代码层面还是不可以,应为new关键字依然在。

ok,说了这么多时间,控制反转和DI(依赖注入)终于入场了,

1、控制反转:上面的代码创建对象的权利的我们自己(通过强编码new的方式),现在我们将创建对象也就是new的权利交给IOC容器,这应该就是所谓的控制反转,以前new的权利,总是在我们的手中,通过new的方法,但是现在new的权利交给了IOC容器

2、依赖注入:通过控制反转移交new的权利之后,我们就可以通过RegisterType(注册类型的方式),告诉IOC容器它可以创建的对象实例,但是创建完实例,之后不能就这么完了,必须进行依赖注入,将

对象实例注入到需要它们的类中,所以修改UserBll.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dal;
using IDal;

namespace Bll
{
    public class UserBll
    {
        private IUserDal _userDal;
        /// <summary>
        /// 通过构造函数,注入依赖
        /// </summary>
        /// <param name="dal">数据层实例</param>
        public UserBll(IUserDal dal)
        {
            this._userDal = dal;
        }
        public string AddUser()
        {
            return _userDal.AddUser();
        }
    }
}

通过构造函数注入的方式,将数据层实例注入到了业务层实例中,现在业务层算是和数据层整个解耦了,现在我们可以通过IOC容器创建对应的数据库实例,并通过IOC容器将创建后的实例注入到业务层实例中!

打开NuGet,输出Unity(这个MS的IOC框架),将它安装到我们的项目中!

到目前位置,已经完成了业务层和数据层的解耦,通过控制反转和依赖注入,具体的变现层调用代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Bll;
using IDal;
using Microsoft.Practices.Unity;

namespace Web
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            UnityContainer container = new UnityContainer();
            container.RegisterType<IDal.IUserDal, OracleDal.UserDal>();//向容器中注册数据库实例类型,并在运行时通过IOC容器创建数据层实例
            UserBll bll = container.Resolve<UserBll>();//将创建完的实例注入到对应的业务类中
            Response.Write(bll.AddUser());
        }
    }
}

输出:

二、与反射工厂相比优势在哪?

当然,通过反射工厂同样能解决上面的问题,接下来新建一个工厂类库

Factory.cs代码如下:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using IDal;

namespace DalFactory
{
    /// <summary>
    /// 工厂类
    /// </summary>
    public partial class Factory
    {
        //这里可以通过将值设置到web.config达到动态配置的效果
        private readonly static string _assemblyName = ConfigurationManager.AppSettings["DalAssemblyName"].ToString();//程序集名
        private readonly static string _nsName = ConfigurationManager.AppSettings["DalNsName"].ToString();//命名空间

        /// <summary>
        /// 通过反射生成类型实例
        /// </summary>
        /// <param name="className">命名控件+类型名</param>
        /// <returns></returns>
        private static object CreateInstance(string className)
        {
            var Assembly = System.Reflection.Assembly.Load(_assemblyName);//加载程序集
            return Assembly.CreateInstance(className);//生成实例
        }
    }

    public partial class Factory
    {
        public static IUserDal CreateUserDal()
        {
            string className = _nsName + ".UserDal";
            return CreateInstance(className) as IUserDal;
        }
    }
}

通过反射和配置文件的方法同样能完成数据层和业务层之间的解耦。

业务层UserBll.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dal;
using DalFactory;
using IDal;

namespace Bll
{
    public class UserBll
    {
        private IUserDal _userDal;

        public UserBll()
        {
            this._userDal = Factory.CreateUserDal();
        }
        public string AddUser()
        {
            return _userDal.AddUser();
        }
    }
}

表现层的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Bll;
using IDal;
using Microsoft.Practices.Unity;

namespace Web
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            UserBll bll = new UserBll();
            Response.Write(bll.AddUser());
        }
    }
}

输出:

通过修改配置文件就能完成数据层的切换,从而业务层和数据层就完成了解耦,但是现在的项目架构虽然只有三个层,但是如果后期项目扩展之后往往不止三层而是n层,为了项目的扩展性,我们往往会将各层之间都解耦,假设有10层,那我们就要写10个工厂类,这个时候工厂就泛滥了,所以工厂的弊端很明显。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏blackheart的专栏

[C#6] 8-异常增强

0. 目录 C#6 新增特性目录 1. 在catch和finally块中使用await 在C#5中引入一对关键字await/async,用来支持新的异步编程模型...

20750
来自专栏智能大石头

老瓶装新酒 - C#调用WM手机发送短信(源码)

一些系统,需要能够发送短信,量很小,平均每日10条。 运营商平台太贵,白名单很严格,小额只能发省内; 各短信平台有各种限制,大事件前后会关闭; 飞信以前可以用W...

25050
来自专栏GreenLeaves

C#核编之System.Environment类

      在前面的例子中用来了Environment.GetCommandLineArgs()这个方法,这个方法就是获取用户的命令行输入,是Environme...

26070
来自专栏草根专栏

使用两种方法让 ASP.NET Core 实现遵循 HATEOAS 结构的 RESTful API

HATEOAS(Hypermedia as the engine of application state)是 REST 架构风格中最复杂的约束,也是构建成熟 ...

589110
来自专栏博客园

使用异步操作时的注意要点(翻译)

在使用异步方法中最好不要使用void当做返回值,无返回值也应使用Task作为返回值,因为使用void作为返回值具有以下缺点

14320
来自专栏菩提树下的杨过

MVC RC2中关于HtmlHelper给DropDownList设置初始选中值的问题

Asp.Net MVC RC2中Helper提供的DropDownList好象并不太好用,特别想给下拉框设置初始选中值的时候(可能我还没找到正确的方法) 小试了...

25750
来自专栏草根专栏

用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(3)

由于本文主要是讲VSCode开发等, 所以相关等一些angular/.net core的知识就相对少讲点. 我把需求改一下, 如图: ? 由于efcore目前还...

39390
来自专栏Java成神之路

Java钉钉开发_02_免登授权(身份验证)

将所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式 (即 key1=value1&key2=value2…)拼接成字符串...

42620
来自专栏林德熙的博客

win10 uwp 异步转同步 使用的条件使用方法使用Task.Wait 时需要小心死锁

在本文开始,我必须告诉大家,这个方法可能立即死锁,所以使用的时候需要满足下面的条件

24720
来自专栏jessetalks

由浅入深表达式树(完结篇)重磅打造 Linq To 博客园

  一个多月之后,由浅入深表达式系列的最后一篇终于要问世了。想对所有关注的朋友说声:“对不起,我来晚了!” 希望最后一篇的内容对得起这一个月时间的等待。在学习完...

46660

扫码关注云+社区

领取腾讯云代金券