| 49 | | IoC就是 Inversion of Control,控制反轉。 |
| 50 | | |
| 51 | | 在Java開發過程中,IoC意謂著將你設計好的類別交給系統去控制,而不是在你的類別內部自己控制。 |
| 52 | | |
| 53 | | 實現IoC有兩種方式:Dependency Injection與Service Locator, |
| 54 | | |
| 55 | | Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入,依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」 |
| 56 | | |
| 57 | | 看看下面這個程式: |
| | 50 | Spring 的核心概念是 IoC,IoC 的抽象概念是「依賴關係的轉移」,像是「高層模組不應該依賴低層模組,而是模組都必須依賴於抽象」是 IoC 的一種表現,「實現必須依賴抽象,而不是抽象依賴實現」也是 IoC 的一種表現,「應用程式不應依賴於容器,而是容器服務於應用程式」也是 IoC 的一種表現。 |
| | 51 | |
| | 52 | IoC 全名 Inversion of Control,如果中文硬要翻譯過來的話,就是「控制反轉」。 |
| | 53 | |
| | 54 | 初看 IoC,從字面上不容易瞭解其意義,我覺得要瞭解 IoC,要先從 Dependency Inversion 開始瞭解,也就是依賴關係的反轉。 |
| | 55 | |
| | 56 | Dependency Inversion 簡單的說,在模組設計時,高層的抽象模組通常是與業務相關的模組,它應該具有重用性,而不依賴於低層的實作模組,例如如果低層模組原先是軟碟存取模式,而高層模組是個存檔備份的需求,如果高層模組直接叫用低層模組的函式,則就對低層模組產生了依賴關係。 |
| | 57 | |
| | 58 | 舉個例子,例如下面這個程式: |
| | 59 | |
| | 60 | {{{ |
| | 61 | #!java |
| | 62 | #include <floppy.h> |
| | 63 | .... |
| | 64 | void save() { |
| | 65 | .... |
| | 66 | saveToFloppy() |
| | 67 | } |
| | 68 | } |
| | 69 | }}} |
| | 70 | 由於save()程式依賴於依賴於saveToFloppy(),如果今天要更換低層的存儲模組為Usb碟,則這個程式沒有辦法重用,必須加以修改才行,低層模組的更動造成了高層模組也必須跟著更動,這不是一個好的設計方式,在設計上希望模組都依賴於模組的抽象,這樣才可以重用高層的業務設計。 |
| | 71 | |
| | 72 | 如果以物件導向的方式來設計,依賴反轉(Dependency Inversion)的解釋變為程式不應依賴實作,而是依賴於抽象,實作必須依賴於抽象。來看看下面這個 Java 程式: |
| | 73 | |
| | 131 | 從這個角度來看,Dependency Inversion 的意思即是程式不依賴於實作,而是程式與實作都要依賴於抽象。 |
| | 132 | |
| | 133 | IoC 的 Control 是控制的意思,其實其背後的意義也是一種依賴關係的轉移,如果A依賴於B,其意義即是B擁有控制權,您想要轉移這種關係,所以依賴關係的反轉即是控制關係的反轉,藉由控制關係的轉移,可以獲得元件的可重用性,在上面的 Java 程式中,整個控制權從實際的 FloppyWriter 轉移至抽象的 IDeviceWriter 介面上,使得BusinessObject、FloppyWriter、UsbDiskWriter 這幾個實現依賴於抽象的 IDeviceWriter 介面。 |
| | 134 | |
| | 135 | 程式的業務邏輯部份應是可以重用的,不應受到所使用框架或容器的影響,因為可能轉移整個業務邏輯至其它的框架或容器,如果業務邏輯過於依賴容器,則轉移至其它的框架或容器時,就會發生困難。 |
| | 136 | |
| | 137 | IoC 在容器的角度,可以用這麼一句好萊塢名言來代表:"Don't call me, I'll call you." 以程式的術語來說的話,就是「不要向容器要求您所需要的(物件)資源,容器會自動將這些物件給您!」。IoC 要求的是容器不侵入應用程式本身,應用程式本身提供好介面,容器可以透過這些介面將所需的資源注至至程式中,應用程式不向容器主動要求資源,故而不會依賴於容器的元件,應用程式本身不會意識到正被容器使用,可以隨時從容器中脫離轉移而不用作任何的修改,而這個特性正是一些業務邏輯中間件最需要的。 |
| | 138 | |
| | 139 | == Dependency Injection == |
| | 140 | |
| | 141 | Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入 |
| | 142 | |
| | 143 | 依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」 |
| | 144 | |
| | 145 | == Type 2 IoC : Setter injection == |
| | 146 | |
| | 147 | BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面: |
| | 148 | {{{ |
| | 149 | #!java |
| | 150 | public interface IDeviceWriter { |
| | 151 | public void saveToDevice(); |
| | 152 | } |
| | 153 | |
| | 154 | public 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 | |
| | 167 | public class FloppyWriter implement IDeviceWriter { |
| | 168 | public void saveToDevice() { |
| | 169 | .... |
| | 170 | // 實際儲存至Floppy的程式碼 |
| | 171 | } |
| | 172 | } |
| | 173 | |
| | 174 | public class UsbDiskWriter implement IDeviceWriter { |
| | 175 | public void saveToDevice() { |
| | 176 | .... |
| | 177 | // 實際儲存至UsbDisk的程式碼 |
| | 178 | } |
| | 179 | } |
| | 180 | |
| | 181 | }}} |
| | 182 | |
| 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 | | |