一、控制反转和依赖注入两者搭配能像反射工厂那样解决程序集之间的耦合问题,下面将从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个工厂类,这个时候工厂就泛滥了,所以工厂的弊端很明显。