| | 1 | IoC模式基本上是一個高層的概念,在 Martin Fowler 的 Inversion of Control Containers and the Dependency Injection pattern 中談到,實現IoC有兩種方式:Dependency Injection與Service Locator,Spring 所採用的是Dependency Injection 來實現 IoC,中文翻譯為依賴注入,依賴注入的意義是:「保留抽象介面,讓組件依賴於抽象介面,當組件要與其它實際的物件發生依賴關係時,藉過抽象介面來注入依賴的實際物件。」 |
| | 2 | |
| | 3 | 看看下面這個程式: |
| | 4 | {{{ |
| | 5 | #!java |
| | 6 | public class BusinessObject { |
| | 7 | private FloppyWriter writer = new FloppyWriter(); |
| | 8 | .... |
| | 9 | |
| | 10 | public void save() { |
| | 11 | ... |
| | 12 | writer.saveToFloppy(); |
| | 13 | |
| | 14 | } |
| | 15 | } |
| | 16 | }}} |
| | 17 | BusinessObject 依賴於實際的 FloppyWriter,為了讓 BusinessObject 獲得重用性,不讓 BusinessObject 直接依賴於實際的 FloppyWriter,而是依賴於抽象的介面: |
| | 18 | |
| | 19 | {{{ |
| | 20 | #!java |
| | 21 | public interface IDeviceWriter { |
| | 22 | public void saveToDevice(); |
| | 23 | } |
| | 24 | |
| | 25 | public class BusinessObject { |
| | 26 | private IDeviceWriter writer; |
| | 27 | |
| | 28 | public void setDeviceWriter(IDeviceWriter writer) { |
| | 29 | this.writer = writer; |
| | 30 | } |
| | 31 | |
| | 32 | public void save() { |
| | 33 | .... |
| | 34 | writer.saveToDevice(); |
| | 35 | } |
| | 36 | } |
| | 37 | |
| | 38 | public class FloppyWriter implement IDeviceWriter { |
| | 39 | public void saveToDevice() { |
| | 40 | .... |
| | 41 | // 實際儲存至Floppy的程式碼 |
| | 42 | } |
| | 43 | } |
| | 44 | |
| | 45 | public class UsbDiskWriter implement IDeviceWriter { |
| | 46 | public void saveToDevice() { |
| | 47 | .... |
| | 48 | // 實際儲存至UsbDisk的程式碼 |
| | 49 | } |
| | 50 | } |
| | 51 | }}} |
| | 52 | |
| | 53 | 如果今天BusinessObject想要與UseDiskWriter物件發生依賴關係,可以這麼建立: |
| | 54 | businessObject.setDeviceWriter(new UsbDiskWriter()); |
| | 55 | |
| | 56 | |
| | 57 | 由於BusinessObject依賴於抽象介面,在需要建立依賴關係時,可以透過抽象介面注入依賴的實際物件。 |
| | 58 | |
| | 59 | 依賴注入在Martin Fowler的文章中談到了三種實現方式:Interface injection、Setter injection 與 Constructor injection。並分別稱其為Type 1 IoC、Type 2 IoC 與 Type 3 IoC。 |
| | 60 | |
| | 61 | 上面的BusinessObject所實現的是Type 2 IoC,透過Setter注入依賴關係,而Type 3 IoC,則在是建構式上注入依賴關係,例如: |
| | 62 | {{{ |
| | 63 | #!java |
| | 64 | public class BusinessObject { |
| | 65 | private IDeviceWriter writer; |
| | 66 | |
| | 67 | public BusinessObject(IDeviceWriter writer) { |
| | 68 | this.writer = writer; |
| | 69 | } |
| | 70 | |
| | 71 | public void save() { |
| | 72 | .... |
| | 73 | writer.saveToDevice(); |
| | 74 | } |
| | 75 | } |
| | 76 | }}} |
| | 77 | |
| | 78 | Spring 鼓勵的是 Setter injection,但也允許您使用 Constructor injection,使用 Setter 或 Constructor 來注入依賴關係視您的需求而定,使用 Constructor 的好處之一是,您可以在建構物件的同時一併完成依賴關係的建立,然而如果要建立的物件關係很多,則會在建構式上留下一長串的參數,這時使用 Setter 會是個不錯的選擇,另一方面,Setter 可以有明確的名稱可以瞭解注入的物件會是什麼,像是setXXX()這樣的名稱會比記憶Constructor上某個參數位置代表某個物件來得好。 |
| | 79 | |
| | 80 | Type 1 IoC是Interface injection,使用Type 1 IoC時會要求實作介面,這個介面是為容器所用的,容器知道介面上所規定的方法,它可以呼叫實作介面的物件來完成依賴關係的注入,例如: |
| | 81 | {{{ |
| | 82 | #!java |
| | 83 | public interface IDependencyInjection { |
| | 84 | public void createDependency(Map dependObjects); |
| | 85 | } |
| | 86 | |
| | 87 | public class BusinessObject implement IDependencyInjection { |
| | 88 | private Map dependObjects; |
| | 89 | |
| | 90 | public void createDependency(Map dependObjects) { |
| | 91 | this.dependObject = dependObjects; |
| | 92 | // 在這邊實現與BusinessObject的依賴關係 |
| | 93 | ...... |
| | 94 | } |
| | 95 | |
| | 96 | public void save() { |
| | 97 | .... |
| | 98 | writer.saveToDevice(); |
| | 99 | } |
| | 100 | } |
| | 101 | }}} |
| | 102 | |
| | 103 | 如果要完成依賴關係注入的物件,必須實現IDependencyInjection介面,並交由容器管理,容器會呼叫被管理物件的createDependency()方法來完成依賴關係的建立。 |
| | 104 | |
| | 105 | 在上面的例子中,Type 1 IoC要求BusinessObject實現特定的介面,這就使得BusinessObject依賴於容器,如果日後BusinessObject要脫離目前這個容器,就必須修改程式,想想在更複雜的依賴關係中產生更多複雜的介面,組件與容器(框架)的依賴會更加複雜,最後使得組件無法從容器中脫離。 |
| | 106 | |
| | 107 | 所以Type 1 IoC具有強的侵入性,使用它來實現依賴注入會使得組件相依於容器(框架),降低組件的重用性。 |
| | 108 | |
| | 109 | Spring的核心是個IoC容器,您可以用Setter或Constructor的方式來實現您的業務物件,至於物件與物件之間的關係建立,則透過組態設定,讓Spring在執行時期根據組態檔的設定來為您建立物件之間的依賴關係,您不必特地撰寫一些Helper來自行建立這些物件之間的依賴關係,這不僅減少了大量的程式撰寫,也降低了物件之間的耦合程度。 |