IT干货网

控制反转---依赖注入理解

shasha 2022年03月15日 编程设计 163 0

在学习Spring的时候,意外找到这个控制反转(IoC)和面向切面(AOP)的容器框架之前,我们先来看一下什么是控制反转(IoC)。

 控制反转(Ioc)和依赖注入(DI)刚听到感到很难理解,平时也程序也很少想到这一点,这几天学Spring的相关资料是看到的最多的是这个概念,网上放狗搜了一下,内容挺多。总算明白了一些。

  Ioc,照我的理解应该是为了满足高内聚低耦合的设计原则,将对象的创建和获取交给外部容器来控制,从外部容器的角度(第三方参照物)来看,达到程序控制权的转移,这样估计好理解了。

  DI,依赖注入,从字面的意思来说是从外部导入的方式实现低耦合,比如构造函数、属性设置等。



  控制反转(Inversion of Control,英文缩写为IoC),也叫依赖注入(Dependency Injection)。我个人认为控制反转的意思是依赖对象发生改变,由最初的类本身来管理依赖对象改变为IoC框架来管理这些对象,使得依赖脱离类本身的控制,从而实现松耦合。

我们先来看一段代码

  1. namespace Dao 
    { 
        public interface IPersonDao 
        { 
            void Save(); 
        } 
        public class PersonDao : IPersonDao 
        { 
            public void Save() 
            { 
                Console.WriteLine("保存 Person"); 
            } 
        } 
    } 
    namespace SpringNetIoC 
    { 
        class Program 
        { 
            private static void NormalMethod() 
            { 
                IPersonDao dao = new PersonDao(); 
                dao.Save(); 
                Console.WriteLine("我是一般方法"); 
            } 
        } 
    }
复制代码
Program必然需要知道IPersonDao 接口和PersonDao类。为了不暴露具体实现,我可以运用 设计模式中的抽象工厂模式(Abstract Factory)来解决。
  1. namespace DaoFactory 
    { 
        public static class DataAccess 
        { 
            public static IPersonDao CreatePersonDao() 
            { 
                return new PersonDao(); 
            } 
        } 
    }
复制代码
FactoryMethod
  1. namespace SpringNetIoC 
    { 
        class Program 
        {        private static void FactoryMethod() 
            { 
                IPersonDao dao = DataAccess.CreatePersonDao(); 
                dao.Save(); 
                Console.WriteLine("我是工厂方法"); 
            } 
        } 
    }
复制代码
这时,Program只需要知道IPersonDao接口和工厂,而不需要知道PersonDao类。然后我们试图想象,要是有这样的工厂框架帮我们管理依赖的对象就好了,于是控制反转出来了。
App.config 
<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
  <configSections> 
    <sectionGroup name="spring"> 
      <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" /> 
      <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" /> 
    </sectionGroup> 
  </configSections> 
  <spring> 
    <context> 
      <resource uri="config://spring/objects" /> 
    </context> 
    <objects xmlns="http://www.springframework.net"> 
      <description>一个简单的控制反转例子</description> 
      <object id="PersonDao" type="Dao.PersonDao, Dao" /> 
    </objects> 
  </spring> 
</configuration>

 
复制代码
Program
  1. using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using Dao; 
    using DaoFactory; 
    using Spring.Context; 
    using Spring.Context.Support; 
    namespace SpringNetIoC 
    { 
        class Program 
        { 
            static void Main(string[] args) 
            { 
                //NormalMethod();  // 一般方法 
                //FactoryMethod();  // 工厂方法 
                IoCMethod();  // IoC方法" 
                Console.ReadLine(); 
            } 
            private static void NormalMethod() 
            { 
                IPersonDao dao = new PersonDao(); 
                dao.Save(); 
                Console.WriteLine("我是一般方法"); 
            } 
            private static void FactoryMethod() 
            { 
                IPersonDao dao = DataAccess.CreatePersonDao(); 
                dao.Save(); 
                Console.WriteLine("我是工厂方法"); 
            } 
            private static void IoCMethod() 
            { 
                IApplicationContext ctx = ContextRegistry.GetContext(); 
                IPersonDao dao = ctx.GetObject("PersonDao") as IPersonDao; 
                if (dao != null) 
                { 
                    dao.Save(); 
                    Console.WriteLine("我是IoC方法"); 
                } 
            } 
        } 
    }
复制代码
一个简单的控制反转程序例子就实现了。

这样从一定程度上解决了Program与PersonDao耦合的问题,但是实际上并没有完全解决耦合,只是把耦合放到了XML 文件中,通过一个容器在需要的时候把这个依赖关系形成,即把需要的接口实现注入到需要它的类中。我个人认为可以把IoC模式看做是工厂模式的升华,可以把 IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的。
 
 
 
 
依赖注入
 
 
1. 控制反转 (Inversion of Control) 与依赖注入 (Dependency Injection)
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
IoC是一个很大的概念,可以用不同的方式来实现。其主要实现方式有两种:<1>依赖查找(Dependency Lookup): 容器提供回调接口和上下文环境给 组件。EJB和Apache Avalon都使用这种方式。<2>依赖注入(Dependency Injection):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。后者是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。
 
图1 控制反转概念结构
依赖注入之所以更流行是因为它是一种更可取的方式:让容器全权负责依赖查询,受管组件只需要暴露JavaBean的setter方法或者带参数的构造子或者接口,使容器可以在初始化时组装 对象的依赖关系。其与依赖查找方式相比,主要优势为:<1>查找定位操作与应用代码完全无关。<2>不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。<3>不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
 
2. 好莱坞原则
IoC体现了好莱坞原则,即“不要打电话过来,我们会打给你”。第一次遇到好莱坞原则是在了解模板方法(Template Mathod)模式的时候,模板方法模式的核心是,基类(抽象类)定义了算法的骨架,而将一些步骤延迟到子类中。
 
图2 模板方法模式类图
 
现在来考虑IoC的实现机制,组件定义了整个流程框架,而其中的一些业务逻辑的实现要借助于其他业务对象的加入,它们可以通过两种方式参与到业务流程中,一种是依赖查找(Dependency Lookup),类似与JDNI的实现,通过JNDI来找到相应的业务对象(代码1),另一种是依赖注入,通过IoC容器将业务对象注入到组件中。
 
3.  依赖查找( Dependency Lookup
下面代码展示了基于JNDI实现的依赖查找机制。
public class MyBusniessObject{ 
  private DataSource ds; 
  private MyCollaborator myCollaborator; 
  
  public MyBusnissObject(){ 
Context ctx = null; 
try{ 
    ctx = new InitialContext(); 
    ds = (DataSource) ctx.lookup(“java:comp/env/dataSourceName”); 
    myCollaborator = 
 (MyCollaborator) ctx.lookup(“java:comp/env/myCollaboratorName”); 
    }……
代码1依赖查找(Dependency Lookup)代码实现
依赖查找的主要问题是,这段代码必须依赖于JNDI环境,所以它不能在应用服务器之外运行,并且如果要用别的方式取代JNDI来查找资源和协作对象,就必须把JNDI代码抽出来重构到一个策略方法中去。
 
4.  依赖注入( Dependency Injection
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。
下面分别演示3中注入机制。
代码2 待注入的业务对象Content.java
package com.zj.ioc.di; 
  
public class Content { 
  
    public void BusniessContent(){ 
       System.out.println("do business"); 
    } 
    
    public void AnotherBusniessContent(){ 
       System.out.println("do another business"); 
    } 
}
MyBusniess类展示了一个业务组件,它的实现需要对象Content的注入。代码3,代码4,代码5,6分别演示构造子注入(Constructor Injection),设值注入(Setter Injection)和接口注入(Interface Injection)三种方式。
 
代码3构造子注入(Constructor Injection)MyBusiness.java
package com.zj.ioc.di.ctor; 
import com.zj.ioc.di.Content; 
  
public class MyBusiness { 
    private Content myContent; 
  
    public MyBusiness(Content content) { 
       myContent = content; 
    } 
    
    public void doBusiness(){ 
       myContent.BusniessContent(); 
    } 
    
    public void doAnotherBusiness(){ 
       myContent.AnotherBusniessContent(); 
    } 
}
 
代码4设值注入(Setter Injection) MyBusiness.java
package com.zj.ioc.di.set; 
import com.zj.ioc.di.Content; 
  
public class MyBusiness { 
    private Content myContent; 
  
    public void setContent(Content content) { 
       myContent = content; 
    } 
    
    public void doBusiness(){ 
       myContent.BusniessContent(); 
    } 
    
    public void doAnotherBusiness(){ 
       myContent.AnotherBusniessContent(); 
    } 
}
 
代码5 设置注入接口InContent.java
package com.zj.ioc.di.iface; 
import com.zj.ioc.di.Content; 
  
public interface InContent { 
    void createContent(Content content); 
}
 
代码6接口注入(Interface Injection)MyBusiness.java
package com.zj.ioc.di.iface; 
import com.zj.ioc.di.Content; 
  
public class MyBusiness implements InContent{ 
    private Content myContent; 
  
    public void createContent(Content content) { 
       myContent = content; 
    } 
    
    public void doBusniess(){ 
       myContent.BusniessContent(); 
    } 
    
    public void doAnotherBusniess(){ 
       myContent.AnotherBusniessContent(); 
    } 
}
 
5. 依赖拖拽 (Dependency Pull)
最后需要介绍的是依赖拖拽,注入的对象如何与组件发生联系,这个过程就是通过依赖拖拽实现。
代码7 依赖拖拽示例
public static void main(String[] args) throws Exception{ 
//get the bean factory 
BeanFactory factory = getBeanFactory(); 
MessageRender mr = (MessageRender) factory.getBean(“renderer”); 
mr.render(); 
}
而通常对注入对象的配置可以通过一个xml文件完成。
使用这种方式对对象进行集中管理,使用依赖拖拽与依赖查找本质的区别是,依赖查找是在业务组件代码中进行的,而不是从一个集中的注册处,特定的地点执行。

评论关闭
IT干货网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

初学SSH 配置+错误总结