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 | | |