依赖注入

来源:互联网 发布:工作笔记软件 编辑:程序博客网 时间:2024/04/30 07:12
     在MVC架构中,整个系统被分离为许多单独的模块(仅仅对于根据用户请求得到合适的数据这一个功能来讲,至少存在M与C的分离),而MVC设计者的初衷之一就是实现各个模块间的松耦合,即相互间的关联性更小。

     先举例如下:
     在一个系统中,我们需要建立一个Email系统,实现Email的发送;
     最简单的实现是直接创建一个EmailSender的模块,但是考虑到系统的扩展性,今后可能会改变Emial发送的方式,或者会提供更多的Emal发送的方式时,更好的方法是先创建一个IEmailSender的接口,提供一系列的方法声明:我们需要一个(或多个)这样的模块,这个(些)模块必须至少能完成接口IEmailSender中承诺的功能,然后让EmailSender实现该接口。
     假如需要完成一个通过发送邮件来找回密码这样的功能模块PassWordHelp时,可以不用直接调用EmailSender,而通过调用IEmailSender里的方法来完成这一功能。如此可以实现对某个功能的调用,和该功能具体的实现进行分离,方便各个模块的扩展。如下:
             
代码实现如下:

public interface IEmailSender
{
     public void SendEmial;
}

 public class MyEmailSender:IEmailSender
{
     public void SendEmail()
     {
          // 实现邮件发送
     }
}

public class PasswordResetHelper 
{
public void ResetPassword() 
{
     IEmailSender mySender = new MyEmailSender();
     // 调用EmailSender 里的方法来实现邮件发送
     mySender.SendEmail();
}
}

     对于模块A而言,当他需要和模块B协同完成某项功能时,在模块A中,需要模块B提供的仅仅是接口,如果需要在模块A中实现模块B的实例来完成相关的任务,就产生了模块B的依赖,依赖的缺陷之一就是如果模块B中提供了某个功能的多种实现,或者以后发生了更改,在A中就必须考虑到这些细节。依赖的缺陷之二就是在测试时,必须在模块A中实现模块B的实例所需的所有信息,这对于单体测试以及整体测试而言,确实不如直接调用模块B提供的接口方便。
     正如上面的例子中,在PasswordResetHelper 模块中,IEmailSender mySender = new MyEmailSender();这句产生了依赖关系,假如今后需要发送邮件的方式不是MyEmailSender提供的方式,就必须去更改PasswordResetHelper 模块中的代码了。很明显,PasswordResetHelper 对EmailSender这种依赖不利于系统扩展。
     对于上述问题的解决方案,就是依依赖注入(DI),或者叫做控制反转(IoC),还是先举例如下:
     修改PasswordResetHelper 中的代码如下:
             public class PasswordResetHelper 
             {
                         public void ResetPassword(IEmailSender mySender) 
                         {
                              mySender.SendEmail();
                         }
             }

     如此,至于PasswordResetHelper 中所需要的IEmailSender 接口中的功能中究竟是哪个模块实现的,这与PasswordResetHelper 无关,只需要把IEmailSender 的某个实例传入就行了。更通行的做法是专门建一个把接口机IEmailSender 和实现IEmailSender 的实例MyEmailSender关联起来的容器,当需要IEmailSender 的实例时,会自动产生MyEmailSender的实例传入ResetPassword方法中。这样实现了把模块A对模块B的依赖抽取到模块A以外去单独管理,然后把依赖的实例传入模块A中,或许这就是"依赖注入"名称的来源吧。
     当然,上例中,是通过在构造方法中把依赖以构造方法的参数的方式来实现的,还可以通过把依赖以Public属性的方式注入,如下:
public class PasswordResetHelper 
{
     private IEmailSender  mySender;
     public PasswordResetHelper ()
     {
         // 通过调用实现
     IEmailSender 接口与具体实例的映射的外部方法来得到IEmailSender 
          mySender=new SomeMethod();
     }
}

     至于如何实现IEmailSender 接口与该接口的具体实例MyEmailSender关联,在需要IEmailSender 的接口时产生对应的实例,最简单的就是自己写代码,通过new来实现。但现在有许多比较好的DI容器,可以很方便的管理接口(包括抽象类)的实例,完成好相关配置后,可以实现自动生成对应的实例传递。



原创粉丝点击