周洪斌
(沙洲职业工学院,江苏 张家港 215600)
摘要:IoC是一种优秀的组件解耦模式,而依赖注入是IoC最流行的实现方式。探讨了依赖注入的三种不同的实现方式:构造器注入、属性注入以及接口注入,并介绍了IoC容器Unity的基本使用方法。
0引言
2015年江苏省高校大学生创新创业训练计划立项项目(201511288009Y)在使用简单三层架构开发数据库应用程序时,一般都是在业务逻辑层直接创建数据访问层相应的对象,如:
public class ProductBLL
{
SqlServerDAL dal = new SqlServerDAL();
public int Add(ProductModel model)
{
return dal.Add(model);
}
}
这种方式下由调用者ProductBLL主动创建被依赖对象SqlServerDAL,然后调用被依赖对象的方法,导致调用者与被依赖对象实现类的硬编码耦合,不利于项目的维护与升级。上述紧耦合的业务逻辑层与数据访问层的关系如图1所示。
如果用户需要将数据库换成Access或者MySQL,需要修改ProductBLL类内部的代码,违反了开放封闭原则。开放封闭原则要求“软件实体(类、模块、函数等)对扩展是开放的,对修改是封闭的”。依赖倒置原则(Dependency Inversion Principle,DIP)要求“高层模块不应该依赖于低层模块,两者应该依赖于抽象”[1]。因此,业务逻辑层的对象不应该直接依赖于数据访问层的具体实现对象,而应该通过数据访问层的抽象接口进行访问,如图2所示。通过引入抽象,对于高层模块而言,低层模块的实现是可替换的。这实际上也是“开放封闭原则”的体现。
1控制反转
DIP作为一种软件设计原则,指明了两个模块之间应该如何依赖。而控制反转(Inversion of Control,IoC)则是一种具体的软件设计模式,明确了如何解除相互依赖的模块之间的耦合。IoC是指应用本身不负责依赖对象的创建和维护,而交给一个外部容器来完成。这样就将控制权由应用转移到了外部,实现了控制权的反转[2]。IoC的作用在于降低组件之间的耦合度,减少组件之间的依赖关系,提高程序的灵活性和可维护性。
依赖注入(Dependency Injection,DI)是IoC模式最流行的实现方式,即由外部容器在运行时动态地将依赖的对象注入到组件中。通过依赖注入,业务逻辑层将不再需要直接创建数据访问层的对象,从而降低了两者之间的耦合度。
2依赖注入
依赖注入提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象。具体的依赖注入可以分为三种形式,即构造器注入、属性注入以及接口注入[3]。
2.1构造器注入
构造器注入,即通过构造方法传递依赖。根据依赖倒置原则,高层模块不应该依赖于低层模块,两者应该依赖于抽象。因此构造方法的参数应该是一个抽象类型。
首先,需要定义一个接口IDataAccess,并在IDataAccess接口中声明一个Add方法,代码如下:
public interface IDataAccess
{
int Add(ProductModel model);
}
然后,在SqlServerDAL类中,实现IDataAccess接口,代码如下:
public class SqlServerDAL:IDataAccess
{
public int Add(ProductModel model)
{
//省略具体数据库操作代码
}
}
接下来,修改ProductBLL类的代码,代码如下:
public class ProductBLL
{
private IDataAccess dal;
//构造器注入
public ProductBLL(IDataAccess dal)
{
this.dal=dal;//传递依赖
}
public int Add(ProductModel model)
{
return dal.Add(model);
}
}
在这里,将依赖对象SqlServerDAL对象的创建和绑定转移到ProductBLL类的外部来实现,这样就解除了两者之间的紧耦合关系。这时,如果要将数据库换成Access数据库时,只需定义一个AccessDAL类,实现IDataAccess接口,然后在外部重新绑定依赖,不需要修改ProductBLL类内部代码即可实现对Access数据库的操作。
2.2属性注入
属性注入,即通过属性来传递依赖。因此,首先需要在依赖类ProductBLL中定义一个属性,代码如下:
public class ProductBLL
{
private IDataAccess dal;
//属性,接受依赖
public IDataAccess Dal
{
set { dal = value; }
get { return dal; }
}
public int Add(ProductModel model)
{
return dal.Add(model);
}
}
然后在外部通过给Dal属性赋值,从而传递依赖。
2.3接口注入
接口注入需要先定义一个接口,包含一个设置依赖的方法。然后由依赖类继承并实现这个接口。
首先定义一个接口,代码如下:
public interface IDependent
{
void SetDependence(IDataAccess dal);//设置依赖项
}
依赖类实现这个接口,代码如下:
public class ProductBLL:IDependent
{
private IDataAccess dal;
//实现接口
public void SetDependence(IDataAccess dal)
{
this.dal = dal;
}
public int Add(ProductModel model)
{
return dal.Add(model);
}
}
外部则通过SetDependence方法传递依赖。相比构造器注入和属性注入,接口注入显得有些复杂,使用也不常见。
3IoC容器
3.1IoC容器概述
一般在小型项目中可以手动创建依赖对象,并将引用传递给被依赖模块。如:
IDataAccess dal=new SqlServerDAL();//在外部创建依赖对象
ProductBLL bll=new ProductBLL(dal);//通过构造器注入依赖
对于大型项目来说,相互依赖的组件比较多,如果还用手动的方式来创建和注入依赖,效率较低。正因如此,IoC容器诞生了。IoC容器实际上是一个依赖注入框架,它包含以下几个功能:
(1)动态创建、注入依赖对象;
(2)管理对象生命周期;
(3)映射依赖关系。
Spring是Java平台广泛使用的IoC容器[4],.NET平台常用的IoC容器包括Unity、Autofac、Spring.NET和Ninject等。Unity是微软公司推出的一款轻量的、可扩展的依赖注入容器,该项目在Codeplex上的地址为http://unity.codeplex.com,可以下载相应的安装包和开发文档[5]。
3.2Unity应用
Unity在实际项目中的使用方法如下[6]:
(1)添加对Microsoft.Practices.Unity.dll、Microsoft.Practices.Unity.Configuration.dll以及Microsoft.Practices.Unity.RegistrationByConvention.dll的引用。
(2)在项目配置文件的
(3)在代码中读取配置信息,并将配置载入到UnityContainer中。
//创建容器
IUnityContainer container = new UnityContainer();
//载入配置信息
container.LoadConfiguration("MyContainer");
//获取指定名称的配置节
UnityConfigurationSection section
= (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
//获取特定配置节下已命名的配置节
section.Configure(container, "MyContainer");
//创建实现了IDataAccess接口的对象
IDataAccess dal = container.Resolve
4结论
IoC能有效降低组件之间的耦合度,提高程序的灵活性和可扩展性。本文围绕如何实现业务逻辑层与数据访问层之间的松散耦合,介绍了构造器注入、属性注入以及接口注入三种不同的依赖注入方式,并介绍了NET平台下常用IoC容器Unity的使用,展示了IoC在软件开发领域的实际应用。
参考文献
[1] 张逸.软件设计精要与模式(第2版)[M].北京:电子工业出版社,2010.
[2] 王程,周安琳.基于Autofac对乳制品安全风险预警系统的扩展设计[J].河北省科学院学报,2013,30(1):14.
[3] 张浩.利用反向控制原则和依赖注入的可复用框架设计解耦方法[J].计算机应用,2010(12):227229.
[4] 周岚.基于Spring框架的IoC模式的设计和实现[J].合肥学院学报(自然科学版),2011,21(1):4953.
[5] 李凤桐.微软企业库组件Unity使用浅析[J].电脑编程技巧与维护,2015(6):1314.
[6] 蒋金楠.ASP.NET MVC4框架揭秘[M].北京:电子工业出版社,2013.