转载:http://www.cnblogs.com/liuhaorain/p/3747470.html
http://blog.csdn.net/qq_22654611/article/details/52606960
http://blog.csdn.net/u010850027/article/details/51931542
面向对象设计(OOD)有助于我们开发出高性能、易扩展以及易复用的程序。其中,OOD有一个重要的思想那就是依赖倒置原则(DIP)(OOD的六大思想:单一原则/开闭原则/依赖倒置/里氏替换/接口隔离/迪米特原则),并由此引申出IoC、DI以及Ioc容器等概念。
依赖倒置原则(DIP):一种软件架构设计的原则(抽象概念)。
控制反转(IoC):一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI):IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
Bob Martins对DIP的定义:
场景一 依赖无倒置(低层模块定义接口,高层模块负责实现)
从上图中,我们发现高层模块的类依赖于低层模块的接口。因此,低层模块需要考虑到所有的接口。如果有新的低层模块类出现时,高层模块需要修改代码,来实现新的低层模块的接口。这样,就破坏了开放封闭原则。
场景二 依赖倒置(高层模块定义接口,低层模块负责实现)
在这个图中,我们发现高层模块定义了接口,将不再直接依赖于低层模块,低层模块负责实现高层模块定义的接口。这样,当有新的低层模块实现时,不需要修改高层模块的代码。
由此,我们可以总结出使用DIP的优点:
系统更柔韧:可以修改一部分代码而不影响其他模块。
系统更健壮:可以修改一部分代码而不会让系统崩溃。
系统更高效:组件松耦合,且可复用,提高开发效率。
DIP是一种 软件设计原则,它仅仅告诉你两个模块之间应该如何依赖,但是它并没有告诉如何做。IoC则是一种 软件设计模式,它告诉你应该如何做,来解除相互依赖模块的耦合。控制反转(IoC),它为相互依赖的组件提供抽象,将依赖(低层模块)对象的获得交给第三方(系统)来控制,即依赖对象不在被依赖模块的类中直接通过new来获取。
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”。
用图例说明一下,传统程序设计如图2-1,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如下图所示:
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。下面我们继续使用以上的例子说明这三种注入方法的区别。
构造函数函数注入,毫无疑问通过构造函数传递依赖。因此,构造函数的参数必然用来接收一个依赖对象。那么参数的类型是什么呢?具体依赖对象的类型?还是一个抽象类型?根据DIP原则,我们知道高层模块不应该依赖于低层模块,两者应该依赖于抽象。那么构造函数的参数应该是一个抽象类型。我们再回到上面那个问题,如何将SqlServerDal对象的引用传递给Order类使用呢(http://www.cnblogs.com/liuhaorain/p/3747470.html)?
初始代码:
public class SqlServerDal //SqlServerDal类,用于数据库的读写。
{
public void Add()
{
Console.WriteLine("在数据库中添加一条订单!");
}
}
public class Order //Order类,负责订单的逻辑处理
{
private readonly SqlServerDal dal = new SqlServerDal();//添加一个私有变量保存数据库操作的对象
public void Add()
{
dal.Add();
}
}
控制台验证代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DIPTest
{
class Program
{
static void Main(string[] args)
{
Order order = new Order();
order.Add();
Console.Read();
}
}
}
为了进行DI改造,首选我们需要定义SqlServerDal的抽象类型IDataAccess,并在IDataAccess接口中声明一个Add方法。
public interface IDataAccess
{
void Add();
}
然后在SqlServerDal类中,实现IDataAccess接口。
public class SqlServerDal:IDataAccess
{
public void Add()
{
Console.WriteLine("在数据库中添加一条订单!");
}
}
接下来,我们还需要修改Order类。
public class Order
{
private IDataAccess _ida;//定义一个私有变量保存抽象
//构造函数注入
public Order(IDataAccess ida)
{
_ida = ida;//传递依赖
}
public void Add()
{
_ida.Add();
}
}
写一个控制台程序验证代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DIPTest
{
class Program
{
static void Main(string[] args)
{
SqlServerDal dal = new SqlServerDal();//在外部创建依赖对象
Order order = new Order(dal);//通过构造函数注入依赖
order.Add();
Console.Read();
}
}
}
从上面我们可以看出,我们将依赖对象SqlServerDal对象的创建和绑定转移到Order类外部来实现,这样就解除了SqlServerDal和Order类的耦合关系。当我们数据库换成Access数据库时,只需定义一个AccessDal类,然后外部重新绑定依赖,不需要修改Order类内部代码,则可实现Access数据库的操作。
public class AccessDal:IDataAccess
{
public void Add()
{
Console.WriteLine("在ACCESS数据库中添加一条记录!");
}
}
然后在控制台程序中重新绑定依赖关系:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DIPTest
{
class Program
{
static void Main(string[] args)
{
AccessDal dal = new AccessDal();//在外部创建依赖对象
Order order = new Order(dal);//通过构造函数注入依赖
order.Add();
Console.Read();
}
}
}
显然,我们不需要修改Order类的代码,就完成了Access数据库的移植,这无疑体现了IoC的精妙。顾名思义,属性注入是通过属性来传递依赖。因此,我们首先需要在依赖类Order中定义一个属性:
public class Order
{
private IDataAccess _ida;//定义一个私有变量保存抽象
//属性,接受依赖
public IDataAccess Ida
{
set { _ida = value; }
get { return _ida; }
}
public void Add()
{
_ida.Add();
}
}
然后在控制台程序中,给属性赋值,从而传递依赖:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DIPTest
{
class Program
{
static void Main(string[] args)
{
AccessDal dal = new AccessDal();//在外部创建依赖对象
Order order = new Order();
order.Ida = dal;//给属性赋值
order.Add();
Console.Read();
}
}
}
我们可以得到上述同样的结果。
相比构造函数注入和属性注入,接口注入显得有些复杂,使用也不常见。具体思路是先定义一个接口,包含一个设置依赖的方法。然后依赖类,继承并实现这个接口。
首先定义一个接口:
public interface IDependent
{
void SetDependence(IDataAccess ida);//设置依赖项
}
public class Order : IDependent
{
private IDataAccess _ida;//定义一个私有变量保存抽象
//实现接口
public void SetDependence(IDataAccess ida)
{
_ida = ida;
}
public void Add()
{
_ida.Add();
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DIPTest
{
class Program
{
static void Main(string[] args)
{
AccessDal dal = new AccessDal();//在外部创建依赖对象
Order order = new Order();
order.SetDependence(dal);//传递依赖
order.Add();
Console.Read();
}
}
}
前面所有的例子中,我们都是通过手动的方式来创建依赖对象,并将引用传递给被依赖模块。比如:
SqlServerDal dal = new SqlServerDal();//在外部创建依赖对象
Order order = new Order(dal);//通过构造函数注入依赖
我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言的的反射编程,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
在spring ioc中有三种依赖注入,分别是:
a、接口注入;
b、setter方法注入;
c、构造方法注入;
public class ClassA {
private InterfaceB clzB;
public void doSomething() {
Ojbect obj = Class.forName(Config.BImplementation).newInstance();
clzB = (InterfaceB)obj;
clzB.doIt();
}
……
}
setter注入模式在实际开发中有非常广泛的应用,setter方法更加直观,我们来看一下spring的配置文件:
接着我们来看一下,setter表示依赖关系的写法:
import com.tgb.spring.dao.UserDao;
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用设值方式赋值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}
构造器注入,即通过构造函数完成依赖关系的设定。我们看一下spring的配置文件:
import com.tgb.spring.dao.UserDao;
public class UserManagerImpl implements UserManager{
private UserDao userDao;
//使用构造方式赋值
public UserManagerImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser(String userName, String password) {
userDao.addUser(userName, password);
}
}