Changes between Version 4 and Version 5 of waue/2011/spring


Ignore:
Timestamp:
Aug 24, 2011, 5:27:29 PM (13 years ago)
Author:
waue
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • waue/2011/spring

    v4 v5  
    2828   * 管理物件的生命週期、物件的組態、相依注入等
    2929   * 控制物件在創建時是以 原型(Prototype) 或 單例(Singleton) 的方式來建立。
    30  * IoC(Inversion of Control)
     30 * IoC = Inversion of Control,控制反轉。
     31   * 在Java開發過程中,IoC意謂著將你設計好的類別交給系統去控制,而不是在你的類別內部自己控制。
    3132   * Spring 的核心概念是IoC,更具體而易懂的名詞是依賴注入(Dependency Injection)
    3233   * 不必自己在程式碼中維護物件的依賴關係,只需在組態檔中加以設定
     
    4748= IoC 模式 =
    4849
    49 IoC就是 Inversion of Control,控制反轉。
    50 
    51 在Java開發過程中,IoC意謂著將你設計好的類別交給系統去控制,而不是在你的類別內部自己控制。
    52 
    53 實現IoC有兩種方式:Dependency Injection與Service Locator,
    54 
    55 Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入,依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」
    56 
    57 看看下面這個程式:
     50Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。
     51
     52IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。
     53
     54初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。
     55
     56Dependency Inversion 簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。
     57
     58舉個例子,例如下面這個程式:
     59
     60{{{
     61#!java
     62#include <floppy.h>
     63....
     64void save() {
     65        ....
     66        saveToFloppy()
     67    }
     68}
     69}}}
     70由於save()程式依賴於依賴於saveToFloppy(),如果今天要更換低層的存儲模組為Usb碟,則這個程式沒有辦法重用,必須加以修改才行,低層模組的更動造成了高層模組也必須跟著更動,這不是一個好的設計方式,在設計上希望模組都依賴於模組的抽象,這樣才可以重用高層的業務設計。
     71
     72如果以物件導向的方式來設計,依賴反轉(Dependency Inversion)的解釋變為程式不應依賴實作,而是依賴於抽象,實作必須依賴於抽象。來看看下面這個 Java 程式:
     73
    5874{{{
    5975#!java
     
    7086}}}
    7187
    72 BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面:
     88在這個程式中,BusinessObject 的存檔依賴於實際的 FloppyWriter,如果今天想要將存檔改為存至 Usb 碟,則必須修改或繼承 BusinessObject 進行擴展,而無法直接使用BusinessObject。
     89
     90如果透過介面的宣告,可以改進此一情況,例如:
    7391
    7492{{{
     
    90108    }
    91109}
    92 
     110}}}
     111
     112這樣一來,BusinessObject 就是可重用的,如果今天有存儲至 Floppy 或 Usb 碟的需求,只要實作 IDeviceWriter 即可,而不用修改 BusinessObject:
     113
     114{{{
     115#!java
    93116public class FloppyWriter implement IDeviceWriter {
    94117    public void saveToDevice() {
     
    106129}}}
    107130
     131從這個角度來看,Dependency Inversion 的意思即是程式不依賴於實作,而是程式與實作都要依賴於抽象。
     132
     133IoC 的 Control 是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,可以獲得元件的可重用性,在上面的 Java 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的 IDeviceWriter 介面上,使得BusinessObject、FloppyWriter、UsbDiskWriter 這幾個實現依賴於抽象的 IDeviceWriter 介面。
     134
     135程式的業務邏輯部份應是可以重用的,不應受到所使用框架或容器的影響,因為可能轉移整個業務邏輯至其它的框架或容器,如果業務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。
     136
     137IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:"Don't call me, I'll call you." 以程式的術語來說的話,就是「不要向容器要求您所需要的(物件)資源,容器會自動將這些物件給您!」。IoC 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。
     138
     139 == Dependency Injection ==
     140
     141Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入
     142
     143依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」
     144
     145 == Type 2 IoC : Setter injection ==
     146
     147BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面:
     148{{{
     149#!java
     150public interface IDeviceWriter {
     151    public void saveToDevice();
     152}
     153
     154public class BusinessObject {
     155    private IDeviceWriter writer;
     156
     157    public void setDeviceWriter(IDeviceWriter writer) {
     158        this.writer = writer;
     159    }
     160
     161    public void save() {
     162        ....
     163        writer.saveToDevice();
     164    }
     165}
     166
     167public class FloppyWriter implement IDeviceWriter {
     168    public void saveToDevice() {
     169        ....
     170        // 實際儲存至Floppy的程式碼
     171    }
     172}
     173
     174public class UsbDiskWriter implement IDeviceWriter {
     175    public void saveToDevice() {
     176        ....
     177        // 實際儲存至UsbDisk的程式碼
     178    }
     179}
     180
     181}}}
     182
    108183如果今天BusinessObject想要與UseDiskWriter物件發生依賴關係,可以這麼建立:
    109 
    110 {{{
    111 #!java
    112184businessObject.setDeviceWriter(new UsbDiskWriter());
    113 }}}
     185
    114186
    115187由於BusinessObject依賴於抽象介面,在需要建立依賴關係時,可以透過抽象介面注入依賴的實際物件。
     
    117189依賴注入在Martin Fowler的文章中談到了三種實現方式:Interface injection、Setter injection 與 Constructor injection。並分別稱其為Type 1 IoC、Type 2 IoC 與 Type 3 IoC。
    118190
    119 上面的BusinessObject所實現的是Type 2 IoC,透過Setter注入依賴關係,而Type 3 IoC,則在是建構式上注入依賴關係,例如:
    120 {{{
    121 #!java
     191上面的BusinessObject所實現的是Type 2 IoC,透過Setter注入依賴關係,
     192
     193 == Type 3 IoC : Constructor injection ==
     194
     195Type 3 IoC,則在是建構式上注入依賴關係,例如:
    122196public class BusinessObject {
    123197    private IDeviceWriter writer;
     
    132206    }
    133207}
    134 }}}
     208
    135209
    136210Spring 鼓勵的是 Setter injection,但也允許您使用 Constructor injection,使用 Setter 或 Constructor 來注入依賴關係視您的需求而定,使用 Constructor 的好處之一是,您可以在建構物件的同時一併完成依賴關係的建立,然而如果要建立的物件關係很多,則會在建構式上留下一長串的參數,這時使用 Setter 會是個不錯的選擇,另一方面,Setter 可以有明確的名稱可以瞭解注入的物件會是什麼,像是setXXX()這樣的名稱會比記憶Constructor上某個參數位置代表某個物件來得好。
    137211
     212 == Type 1 IoC : Interface injection ==
     213
    138214Type 1 IoC是Interface injection,使用Type 1 IoC時會要求實作介面,這個介面是為容器所用的,容器知道介面上所規定的方法,它可以呼叫實作介面的物件來完成依賴關係的注入,例如:
    139 {{{
    140 #!java
    141215public interface IDependencyInjection {
    142216    public void createDependency(Map dependObjects);
     
    157231    }
    158232}
    159 }}}
     233
    160234
    161235如果要完成依賴關係注入的物件,必須實現IDependencyInjection介面,並交由容器管理,容器會呼叫被管理物件的createDependency()方法來完成依賴關係的建立。
     
    167241Spring的核心是個IoC容器,您可以用Setter或Constructor的方式來實現您的業務物件,至於物件與物件之間的關係建立,則透過組態設定,讓Spring在執行時期根據組態檔的設定來為您建立物件之間的依賴關係,您不必特地撰寫一些Helper來自行建立這些物件之間的依賴關係,這不僅減少了大量的程式撰寫,也降低了物件之間的耦合程度。
    168242
    169  == Dependency Injection ==
    170 
    171 Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入,依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」
    172 
    173  == Type 2 IoC : Setter injection ==
    174 
    175 BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面:
    176 {{{
    177 #!java
    178 public interface IDeviceWriter {
    179     public void saveToDevice();
    180 }
    181 
    182 public class BusinessObject {
    183     private IDeviceWriter writer;
    184 
    185     public void setDeviceWriter(IDeviceWriter writer) {
    186         this.writer = writer;
    187     }
    188 
    189     public void save() {
    190         ....
    191         writer.saveToDevice();
    192     }
    193 }
    194 
    195 public class FloppyWriter implement IDeviceWriter {
    196     public void saveToDevice() {
    197         ....
    198         // 實際儲存至Floppy的程式碼
    199     }
    200 }
    201 
    202 public class UsbDiskWriter implement IDeviceWriter {
    203     public void saveToDevice() {
    204         ....
    205         // 實際儲存至UsbDisk的程式碼
    206     }
    207 }
    208 
    209 }}}
    210 
    211 如果今天BusinessObject想要與UseDiskWriter物件發生依賴關係,可以這麼建立:
    212 businessObject.setDeviceWriter(new UsbDiskWriter());
    213 
    214 
    215 由於BusinessObject依賴於抽象介面,在需要建立依賴關係時,可以透過抽象介面注入依賴的實際物件。
    216 
    217 依賴注入在Martin Fowler的文章中談到了三種實現方式:Interface injection、Setter injection 與 Constructor injection。並分別稱其為Type 1 IoC、Type 2 IoC 與 Type 3 IoC。
    218 
    219 上面的BusinessObject所實現的是Type 2 IoC,透過Setter注入依賴關係,
    220 
    221  == Type 3 IoC : Constructor injection ==
    222 
    223 Type 3 IoC,則在是建構式上注入依賴關係,例如:
    224 public class BusinessObject {
    225     private IDeviceWriter writer;
    226 
    227     public BusinessObject(IDeviceWriter writer) {
    228         this.writer = writer;
    229     }
    230 
    231     public void save() {
    232         ....
    233         writer.saveToDevice();
    234     }
    235 }
    236 
    237 
    238 Spring 鼓勵的是 Setter injection,但也允許您使用 Constructor injection,使用 Setter 或 Constructor 來注入依賴關係視您的需求而定,使用 Constructor 的好處之一是,您可以在建構物件的同時一併完成依賴關係的建立,然而如果要建立的物件關係很多,則會在建構式上留下一長串的參數,這時使用 Setter 會是個不錯的選擇,另一方面,Setter 可以有明確的名稱可以瞭解注入的物件會是什麼,像是setXXX()這樣的名稱會比記憶Constructor上某個參數位置代表某個物件來得好。
    239 
    240  == Type 1 IoC : Interface injection ==
    241 
    242 Type 1 IoC是Interface injection,使用Type 1 IoC時會要求實作介面,這個介面是為容器所用的,容器知道介面上所規定的方法,它可以呼叫實作介面的物件來完成依賴關係的注入,例如:
    243 public interface IDependencyInjection {
    244     public void createDependency(Map dependObjects);
    245 }
    246 
    247 public class BusinessObject implement IDependencyInjection {
    248     private Map dependObjects;
    249 
    250     public void createDependency(Map dependObjects) {
    251         this.dependObject = dependObjects;
    252         // 在這邊實現與BusinessObject的依賴關係
    253         ......
    254     }
    255 
    256     public void save() {
    257         ....
    258         writer.saveToDevice();
    259     }
    260 }
    261 
    262 
    263 如果要完成依賴關係注入的物件,必須實現IDependencyInjection介面,並交由容器管理,容器會呼叫被管理物件的createDependency()方法來完成依賴關係的建立。
    264 
    265 在上面的例子中,Type 1 IoC要求BusinessObject實現特定的介面,這就使得BusinessObject依賴於容器,如果日後BusinessObject要脫離目前這個容器,就必須修改程式,想想在更複雜的依賴關係中產生更多複雜的介面,組件與容器(框架)的依賴會更加複雜,最後使得組件無法從容器中脫離。
    266 
    267 所以Type 1 IoC具有強的侵入性,使用它來實現依賴注入會使得組件相依於容器(框架),降低組件的重用性。
    268 
    269 Spring的核心是個IoC容器,您可以用Setter或Constructor的方式來實現您的業務物件,至於物件與物件之間的關係建立,則透過組態設定,讓Spring在執行時期根據組態檔的設定來為您建立物件之間的依賴關係,您不必特地撰寫一些Helper來自行建立這些物件之間的依賴關係,這不僅減少了大量的程式撰寫,也降低了物件之間的耦合程度。
    270