Inversion of Control

      控制反轉Ioc模式(又稱DI:Dependency Injection)就是Inversion of Control,控制反轉。在Java開發中,IoC意味着將你設計好的交給系統去控制,而不是在你的類內部控制。這稱爲控制反轉。

      IoC(Inversion of Control)是近年來興起的一種思想,不僅僅是編程思想。主要是協調各組件間相互的依賴關係,同時大大提高了組件的可移植性,組件的重用機會也變得更多。在傳統的實現中,由程序內部代碼來控制程序之間的關係。我們經常使用new關鍵字來實現兩組鍵間關係的組合,這種實現的方式會造成組件之間耦合(一個好的設計,不但要實現代碼重用,還要將組件間關係解耦)。IoC很好的解決了該問題,它將實現組件間關係從程序內部提到外部容器來管理。也就是說由容器在運行期將組件間的某種依賴關係動態的注入組件中。控制程序間關係的實現交給了外部的容器來完成。即常說的好萊塢原則“Don't call us, we'll call you”。

      Ioc也有稱爲DI(Dependecy Injection 依賴注射),由Martin Fowler的一篇《Inversion of Control Containers and the Dependency Injection pattern》提出。


  分離關注( Separation of Concerns : SOC)是Ioc模式和AOP產生最原始動力,通過功能分解可得到關注點,這些關注可以是 組件Components, 方面Aspects或服務Services。

  從GoF設計模式中,我們已經習慣一種思維編程方式:Interface Driven Design 接口驅動,接口驅動有很多好處,可以提供不同靈活的子類實現,增加代碼穩定和健壯性等等,但是接口一定是需要實現的,也就是如下語句遲早要執行:

  AInterface a = new AInterfaceImp();

  AInterfaceImp是接口AInterface的一個子類,Ioc模式可以延緩接口的實現,根據需要實現,有個比喻:接口如同空的模型套,在必要時,需要向模型套注射石膏,這樣才能成爲一個模型實體,因此,我們將人爲控制接口的實現成爲“注射”。

  Ioc英文爲 Inversion of Control,即反轉模式,這裏有著名的好萊塢理論:你呆着別動,到時我會找你。

  其實Ioc模式也是解決調用者和被調用者之間的一種關係,上述AInterface實現語句表明當前是在調用被調用者AInterfaceImp,由於被調用者名稱寫入了調用者的代碼中,這產生了一個接口實現的原罪:彼此聯繫,調用者和被調用者有緊密聯繫,在UML中是用依賴 Dependency 表示。

  但是這種依賴在分離關注的思維下是不可忍耐的,必須切割,實現調用者和被調用者解耦,新的Ioc模式 Dependency Injection 模式由此產生了, Dependency Injection模式是依賴注射的意思,也就是將依賴先剝離,然後在適當時候再注射進入。

一、Ioc模式(Dependency Injection模式)有三種:

      第一種類型 從JNDI或ServiceManager等獲得被調用者,這裏類似ServiceLocator模式。 1. EJB/J2EE,2. Avalon(Apache的一個複雜使用不多的項目)
      第二種類型 使用JavaBeans的setter方法 1. Spring Framework,2. WebWork/XWork
      第三種類型 在構造方法中實現依賴 1. PicoContainer,2. HiveMind

  有過EJB開發經驗的人都知道,每個EJB的調用都需要通過JNDI尋找到工廠性質的Home接口,在我的教程EJB是什麼章節中,我也是從依賴和工廠模式角度來闡述EJB的使用。

  在通常傳統情況下,爲了實現調用者和被調用者解耦,分離,一般是通過工廠模式實現的,下面將通過比較工廠模式和Ioc模式不同,加深理解Ioc模式。

二、工廠模式和Ioc

  假設有兩個類B 和 C:B作爲調用者,C是被調用者,在B代碼中存在對C的調用:

public class B{
   private C comp;
  ......
}
 

  實現comp實例有兩種途徑:單態工廠模式和Ioc。

工廠模式實現如下:

public class B{
   private C comp;
  private final static MyFactory myFactory = MyFactory.getInstance();

  public B(){
    this.comp = myFactory.createInstanceOfC();

  }
   public void someMethod(){
    this.comp.sayHello();
  }
  ......
}
 

特點:

每次運行時,MyFactory可根據配置文件XML中定義的C子類實現,通過createInstanceOfC()生成C的具體實例。
使用Ioc依賴性注射( Dependency Injection )實現Picocontainer如下,B類如同通常POJO類,如下:

public class B{
   private C comp;
  public B(C comp){
    this.comp = comp;
   }
   public void someMethod(){
    this.comp.sayHello();
   }
  ......
}
 

假設C接口/類有有一個具體實現CImp類。當客戶端調用B時,使用下列代碼:

public class client{
   public static void main( String[] args ) {
    DefaultPicoContainer container = new DefaultPicoContainer();
    container.registerComponentImplementation(CImp.class);
    container.registerComponentImplementation(B.class);
    B b = (B) container.getComponentInstance(B.class);
    b.someMethod();
   }
}
 

  因此,當客戶端調用B時,分別使用工廠模式和Ioc有不同的特點和區別:

  主要區別體現在B類的代碼,如果使用Ioc,在B類代碼中將不需要嵌入任何工廠模式等的代碼,因爲這些工廠模式其實還是與C有些間接的聯繫,這樣,使用Ioc徹底解耦了B和C之間的聯繫。

  使用Ioc帶來的代價是:需要在客戶端或其它某處進行B和C之間聯繫的組裝。

  所以,Ioc並沒有消除B和C之間這樣的聯繫,只是轉移了這種聯繫。
  這種聯繫轉移實際也是一種分離關注,它的影響巨大,它提供了AOP實現的可能。

Ioc和AOP
  AOP我們已經知道是一種面向切面的編程方式,由於Ioc解放自由了B類,而且可以向B類實現注射C類具體實現,如果把B類想像成運行時的橫向動作,無疑注入C類子類就是AOP中的一種Advice,如下圖:

 

  通過下列代碼說明如何使用Picocontainer實現AOP,該例程主要實現是記錄logger功能,通過Picocontainer可以使用簡單一行,使所有的應用類的記錄功能激活。

首先編制一個記錄接口:

public interface Logging {

  public void enableLogging(Log log);

}
 

有一個LogSwitcher類,主要用來激活具體應用中的記錄功能:

import org.apache.commons.logging.Log;
public class LogSwitcher
{
  protected Log m_log;
  public void enableLogging(Log log) {
    m_log = log;
    m_log.info("Logging Enabled");
  }
}

一般的普通應用JavaBeans都可以繼承這個類,假設PicoUserManager是一個用戶管理類,代碼如下:

public class PicoUserManager extends LogSwitcher
{
  ..... //用戶管理功能
}
public class PicoXXXX1Manager extends LogSwitcher
{

  ..... //業務功能
}
public class PicoXXXX2Manager extends LogSwitcher
{

  ..... //業務功能
}
 

注意LogSwitcher中Log實例是由外界賦予的,也就是說即將被外界注射進入,下面看看使用Picocontainer是如何注射Log的具體實例的。


DefaultPicoContainer container = new DefaultPicoContainer();
container.registerComponentImplementation(PicoUserManager.class);
container.registerComponentImplementation(PicoXXXX1Manager.class);
container.registerComponentImplementation(PicoXXXX2Manager.class);
.....

Logging logging = (Logging) container.getComponentMulticaster();

logging.enableLogging(new SimpleLog("pico"));//激活log


 

  由上代碼可見,通過使用簡單一行logging.enableLogging()方法使所有的應用類的記錄功能激活。這是不是類似AOP的advice實現?

  總之,使用Ioc模式,可以不管將來具體實現,完全在一個抽象層次進行描述和技術架構,因此,Ioc模式可以爲容器、框架之類的軟件實現提供了具體的實現手段,屬於架構技術中一種重要的模式應用。J道的JdonSD框架也使用了Ioc模式。

參考資料:

Inversion of Control Containers and the Dependency Injection pattern
A Brief Introduction to IoC
Ioc容器的革命性優點
Java企業系統架構選擇考量
IOC模式的思考和疑問

三、IoC的幾種實現類型

(1)Type1接口注入

通常做法是利用接口將調用者與實現者分離。
public class Sport {
  private InterfaceBall ball; //InterfaceBall是定義的接口
  public void init() {
    //Basketball實現了InterfaceBall接口
    ball = (InterfaceBall) Class.forName("Basketball").newInstance();
  }
}
Sport類在編譯期依賴於InterfaceBall的實現,爲了將調用者與實現者分離,我們動態生成Basketball類並通了強制類型轉換爲InterfaceBall。Apache Avalon是一個典型的Type1型IoC容器。

(2)setter方法注入

在類中暴露setter方法來實現依賴關係。
public class Sport {
  private InterfaceBall ball;
  public void setBall(InterfaceBall arg) {
    ball = arg;
  }
}
這種方式對已經習慣了JavaBean的程序員而言,更顯直觀。Spring就是實現了該類型的輕量級容器。

(3)Type3構造子注入

即通過構造方法完成依賴關係。
public class Sport {
  private InterfaceBall ball;
  public Sport(InterfaceBall arg) {
    ball = arg;
  }
}
可以看到,通過類的構造方法建立依賴關係。由於Type3在構造期就形成了對象的依賴關係,即存對象的重用變的困難。有些框架需要組件提供一個默認的構造方法,此時就體現了Type3的侷限性。通常所有的參數都是通過構造方法注入的,當對象間的依賴關係較多時,構造方法就顯的比較複雜,不利於單元測試。PicoContainer就是實現了Type3依賴注入模式的輕量級容器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章