控制反轉---依賴注入理解


在學習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文件完成。
使用這種方式對對象進行集中管理,使用依賴拖拽與依賴查找本質的區別是,依賴查找是在業務組件代碼中進行的,而不是從一個集中的注冊處,特定的地點執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM