通过 人打电话 来谈谈自己对IOC的理解
版本1.0
public class Person { public AndroidPhone Phone { get; set; } public void CallForSomebody() { Phone.Call(); } } public class AndroidPhone { public void Call() { Console.WriteLine($"{this.GetType().Name} is calling"); } } class Program { static void Main(string[] args) { Person person = new Person { Phone = new AndroidPhone() }; person.CallForSomebody(); Console.ReadKey(); } }
如果这时候,想给这个人一部苹果手机,那么必须要修改 Person 类, 将属性Phone的类型改成:ApplePhone.这显然违背了OOP思想的"对修改封闭,对扩展开放"原则.同时该代码也不符合"接口分离原则",于是乎做如下调整.
版本2.0
public class Person { //定义 Phone 为 接口 IPhone 类型 public IPhone Phone { get; set; } public void CallForSomebody() { Phone.Call(); } } public interface IPhone { void Call(); } public class ApplePhone : IPhone { public void Call() { Console.WriteLine($"{this.GetType().Name} is calling"); } } public class AndroidPhone : IPhone { public void Call() { Console.WriteLine($"{this.GetType().Name} is calling"); } } 控制台程序: Person person = new Person(); //给这个人一部安卓手机 person.Phone = new AndroidPhone(); person.CallForSomebody(); //给这个人一部苹果手机 person.Phone = new ApplePhone(); person.CallForSomebody();
如果需要给诺基亚手机,只需要在创建一个诺基亚Phone类,实现 IPhone 接口,而不需要修改底层代码.
依赖注入有三种方式:
public class Person { //属性注入 public IPhone Phone { get; set; } //构造函数注入 public Person(IPhone phone) { this.Phone = phone; } //方法注入 public void MethodInject(IPhone phone) { this.Phone = phone; } public void CallForSomebody() { Phone.Call(); } }
版本2.0虽然不需要修改底层代码,但是控制台的代码依然需要修改,我们需要将 new AndridPhone() 更改成 new ApplePhone()
版本3.0 ( 利用 Unity )
学习新东西都是从 hello world 开始,先来一段 Unity 的 hello world
//创建一个IOC容器 IUnityContainer container = new UnityContainer(); //想要安卓手机就注册安卓手机 //container.RegisterType<IPhone, AndroidPhone>(); //想要苹果手机就注册苹果手机 container.RegisterType<IPhone, ApplePhone>(); IPhone phone = container.Resolve<IPhone>(); Person person = new Person(phone); person.CallForSomebody();
这段代码其实 跟 版本2.0没什么区别,甚至还复杂了,版本2.0是改 new AndroidPhone() , 这里是改 container.RegisterType<IPhone, AndroidPhone>()
版本4.0
新建一个配置文件:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <containers> <container name="unityContainer"> <!--type,mapTo:逗号左边是类的完全限定名,即命名空间+类名;逗号右边是程序集名称--> <register type="IOCUnity.IPhone,IOCUnity" mapTo="IOCUnity.AndroidPhone,IOCUnity"></register> </container> </containers> </unity> </configuration>
控制台代码如下:
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap { ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + @"UnityXml\UnityConfig.xml") }; Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap,ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); IUnityContainer container = new UnityContainer(); section.Configure(container, "unityContainer"); IPhone phone = container.Resolve<IPhone>(); Person person = new Person(phone); person.CallForSomebody();
如果要换成 ApplePhone , 只需要将 配置文件中 mapTo="IOCUnity.AndroidPhone,IOCUnity" 更改成 mapTo="IOCUnity.ApplePhone,IOCUnity"> 即可,源代码不需要做任何修改
版本4.5
配置文件修改: 添加 name 标记
<register type="IOCUnity.IPhone,IOCUnity" mapTo="IOCUnity.ApplePhone,IOCUnity" name="Apple"></register> <register type="IOCUnity.IPhone,IOCUnity" mapTo="IOCUnity.AndroidPhone,IOCUnity" name="Android"></register>
//创建一个安卓手机 //IPhone phone = container.Resolve<IPhone>("Android"); //创建一个苹果手机 IPhone phone = container.Resolve<IPhone>("Android");
版本5.0
控制台代码:
ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap { ExeConfigFilename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory + @"UnityXml\UnityConfig.xml") }; Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection(UnityConfigurationSection.SectionName); IUnityContainer container = new UnityContainer(); section.Configure(container, "unityContainer"); Person person = container.Resolve<Person>(); /* 上面创建了一个person,虽然我们没有给它的属性Phone赋值,但实际上 Unity 已经帮我们注入了. Unity 有三种特性 分别对应三种注入方式: [InjectionConstructor] 构造函数注入 [dependency] 属性注入 [InjectionMethod] 方法注入 如果一个特性都不打,那么Unity 默认会采用 构造函数注入,并且是选用入参最多的那个构造函数注入.因此,推荐采用 构造函数注入,因为它是无侵入式的,不需要打任何特性. */ person.CallForSomebody();
public class Person { //属性注入 [Dependency] public IPhone Phone { get; set; } //构造函数注入 [InjectionConstructor] public Person(IPhone phone) { this.Phone = phone; } //方法注入 [InjectionMethod] public void MethodInject(IPhone phone) { this.Phone = phone; } public void CallForSomebody() { Phone.Call(); } }
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <containers> <container name="unityContainer"> <!--type,mapTo:逗号左边是类的完全限定名,即命名空间+类名;逗号右边是程序集名称--> <register type="IOCUnity.IPhone,IOCUnity" mapTo="IOCUnity.ApplePhone,IOCUnity"></register> <register type="IOCUnity.Person,IOCUnity" mapTo="IOCUnity.Person,IOCUnity"></register> </container> </containers> </unity> </configuration>