IOC:英文全稱:Inversion of Control,中文名稱:控制反轉,它還有個名字叫依賴注入(Dependency Injection)。
作用:將各層的對象以松耦合的方式組織在一起,解耦,各層對象的調用完全面向接口。當系統重構的時候,代碼的改寫量將大大減少。
理解依賴注入:
當一個類的實例需要另一個類的實例協助時,在傳統的程序設計過程中,通常有調用者來創建被調用者的實例。然而采用依賴注入的方式,創建被調用者的工作不再由調用者來完成,因此叫控制反轉,創建被調用者的實例的工作由IOC容器來完成,然后注入調用者,因此也稱為依賴注入。
舉個有意思的例子(來源於互聯網)
1.青梅竹馬:很久很久以前,有個有錢的地主家的一閨女叫Lily,她老爸把她許配給縣太爺的兒子Jimmy,屬於指腹為婚,Lily非常喜歡kiss,但是只能kiss Jimmy
- public class Lily{
- public Jimmy jimmy;
- public Girl()
- {
- jimmy=new Jimmy();
- }
- public void Kiss()
- {
- jimmy.Kiss();
- }
- }
- public class Jimmy
- {
- public void Kiss()
- {
- Console.WriteLine("kissing");
- }
- }
這樣導致Lily對Jimmy的依賴性非常強,緊耦合。
2.親友介紹:經常Kiss同一個人令Lily有些厭惡了,她想嘗試新人,於是與Jimmy分手了,通過親朋好友(中間人)來介紹
- public class Lily{
- public Boy boy;
- public Girl()
- {
- boy=BoyFactory.createBoy();
- }
- public void Kiss()
- {
- boy.Kiss();
- }
- }
親友介紹,固然是好。如果不滿意,盡管另外換一個好了。但是,親友BoyFactory經常是以Singleton的形式出現,不然就是,存在於Globals,無處不在,無處不能。實在是太繁瑣了一點,不夠靈活。我為什么一定要這個親友摻和進來呢?為什么一定要付給她介紹費呢?萬一最好的朋友愛上了我的男朋友呢?
3.父母包辦:一切交給父母,自己不用非吹灰之力,Lily在家只Kiss
- public class Lily{
- public Boy boy;
- public Girl(Boy boy)
- {
- this.boy=boy;
- }
- public void Kiss()
- {
- this.boy.Kiss();
- }
- }
Well,這是對Girl最好的方法,只要想辦法賄賂了Girl的父母,並把Boy交給他。那么我們就可以輕松的和Girl來Kiss了。看來幾千年傳統的父母之命還真是有用哦。至少Boy和Girl不用自己瞎忙乎了。這就是IOC,將對象的創建和獲取提取到外部。由外部容器提供需要的組件。
在設計模式中我們應該還知道依賴倒轉原則,應是面向接口編程而不是面向功能實現,好處是:多實現可以任意切換,我們的Boy應該是實現Kissable接口。這樣一旦Girl不想kiss可惡的Boy的話,還可以kiss可愛的kitten和慈祥的grandmother
好在.net中微軟有一個輕量級的IoC框架Unity,支持構造器注入,屬性注入,方法注入如下圖所示
具體使用方法如下圖所示
- using System;
- using Microsoft.Practices.Unity;
- namespace ConsoleApplication9
- {
- class Program
- {
- static void Main(string[] args)
- {
- //創建容器
- IUnityContainer container=new UnityContainer();
- //注冊映射
- container.RegisterType<IKiss, Boy>();
- //得到Boy的實例
- var boy = container.Resolve<IKiss>();
- Lily lily = new Lily(boy);
- lily.kiss();
- }
- }
- public interface IKiss
- {
- void kiss();
- }
- public class Lily:IKiss
- {
- public IKiss boy;
- public Lily(IKiss boy)
- {
- this.boy=boy;
- }
- public void kiss()
- {
- boy.kiss();
- Console.WriteLine("lily kissing");
- }
- }
- public class Boy : IKiss
- {
- public void kiss()
- {
- Console.WriteLine("boy kissing");
- }
- }
- }
如果采用配置文件注冊的話
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <configSections>
- <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
- </configSections>
- <unity>
- <containers>
- <container name="defaultContainer">
- <register type="命名空間.接口類型1,命名空間" mapTo="命名空間.實現類型1,命名空間" />
- <register type="命名空間.接口類型2,命名空間" mapTo="命名空間.實現類型2,命名空間" />
- </container>
- </containers>
- </unity>
- </configuration>
配置的后台代碼:
- UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName)
- as UnityConfigurationSection;
- configuration.Configure(container, "defaultContainer");
可以通過方法ResolveAll來得到所有注冊對象的實例:
var Instances = container.Resolve<IKiss>();
Martin Fowler在那篇著名的文章《Inversion of Control Containers and the Dependency Injection pattern》中將具體依賴注入划分為三種形式,即構造器注入、屬性(設置)注入和接口注入,習慣將其划分為一種(類型)匹配和三種注入:
- 類型匹配(Type Matching):雖然我們通過接口(或者抽象類)來進行服務調用,但是服務本身還是實現在某個具體的服務類型中,這就需要某個類型注冊機制來解決服務接口和服務類型之間的匹配關系;
- 構造器注入(Constructor Injection):IoC容器會智能地選擇選擇和調用適合的構造函數以創建依賴的對象。如果被選擇的構造函數具有相應的參數,IoC容器在調用構造函數之前解析注冊的依賴關系並自行獲得相應參數對象;
- 屬性注入(Property Injection):如果需要使用到被依賴對象的某個屬性,在被依賴對象被創建之后,IoC容器會自動初始化該屬性;
- 方法注入(Method Injection):如果被依賴對象需要調用某個方法進行相應的初始化,在該對象創建之后,IoC容器會自動調用該方法。
我們創建一個控制台程序,定義如下幾個接口(IA、IB、IC和ID)和它們各自的實現類(A、B、C、D)。在類型A中定義了3個屬性B、C和D,其類型分別為接口IB、IC和ID。其中屬性B在構在函數中被初始化,以為着它會以構造器注入的方式被初始化;屬性C上應用了DependencyAttribute特性,意味着這是一個需要以屬性注入方式被初始化的依賴屬性;屬性D則通過方法Initialize初始化,該方法上應用了特性InjectionMethodAttribute,意味着這是一個注入方法在A對象被IoC容器創建的時候會被自動調用。
- public interface IA { }
- public interface IB { }
- public interface IC { }
- public interface ID { }
- public class A : IA
- {
- public IB B { get; set; }
- [Dependency]
- public IC C { get; set; }
- public ID D { get; set; }
- public A(IB b)
- {
- this.B = b;
- }
- [InjectionMethod]
- public void Initalize(ID d)
- {
- this.D = d;
- }
- }
- public class B : IB { }
- public class C : IC { }
- public class D : ID { }
然后我們為該應用添加一個配置文件,並定義如下一段關於Unity的配置。這段配置定義了一個名稱為defaultContainer的Unity容器,並在其中完成了上面定義的接口和對應實現類之間映射的類型匹配。
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <configSections>
- <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/>
- </configSections>
- <unity>
- <containers>
- <container name="defaultContainer">
- <register type="UnityDemo.IA,UnityDemo" mapTo="UnityDemo.A, UnityDemo"/>
- <register type="UnityDemo.IB,UnityDemo" mapTo="UnityDemo.B, UnityDemo"/>
- <register type="UnityDemo.IC,UnityDemo" mapTo="UnityDemo.C, UnityDemo"/>
- <register type="UnityDemo.ID,UnityDemo" mapTo="UnityDemo.D, UnityDemo"/>
- </container>
- </containers>
- </unity>
- </configuration>
最后在Main方法中創建一個代表IoC容器的UnityContainer對象,並加載配置信息對其進行初始化。然后調用它的泛型的Resolve方法創建一個實現了泛型接口IA的對象。最后將返回對象轉變成類型A,並檢驗其B、C和D屬性是否是空
- class Program
- {
- static void Main(string[] args)
- {
- UnityContainer container = new UnityContainer();
- UnityConfigurationSection configuration = ConfigurationManager.GetSection(UnityConfigurationSection.SectionName) as UnityConfigurationSection;
- configuration.Configure(container, "defaultContainer");
- A a = container.Resolve<IA>() as A;
- if (null!=a)
- {
- Console.WriteLine("a.B==null?{0}",a.B==null?"Yes":"No");
- Console.WriteLine("a.C==null?{0}", a.C == null ? "Yes" : "No");
- Console.WriteLine("a.D==null?{0}", a.D == null ? "Yes" : "No");
- }
- }
- }
從如下給出的執行結果我們可以得到這樣的結論:通過Resolve<IA>方法返回的是一個類型為A的對象,該對象的三個屬性被進行了有效的初始化。這個簡單的程序分別體現了接口注入(通過相應的接口根據配置解析出相應的實現類型)、構造器注入(屬性B)、屬性注入(屬性C)和方法注入(屬性D)
a.B == null ? No
a.C == null ? No
a.D == null ? No
出處:https://www.cnblogs.com/zhangchenliang/archive/2013/01/08/2850970.html
https://www.cnblogs.com/artech/archive/2011/09/15/UnityDemo.html