通用技术 控制反转、依赖注入、生命周期、反射

SinDynasty · March 15, 2018 · Last by FelixKang replied at May 07, 2018 · 1330 hits

控制反转

控制反转是一种思想,其含义是将对象的创建转移至框架上,或者说是当我们需要用到某个类的实例时,我们只需告诉框架我们所需要的实例类型,框架就会自动提供我们相应类型的实例,当然至于这个框架是怎么创建出这个实例的,我们并不需要知道。

using System;
using System.Collections.Generic;

namespace Test
{
public class Test
{
public static void Main(string[] Args)
{
InstanceProvider Provider = new InstanceProvider(Initialise);
object Instance = Provider.GetInstance("Test.Test");
}//void Main(string[] Args)

public static void Initialise(Dictionary<string, object> InstanceDictionary)
{
InstanceDictionary.Add("Test.Test", new Test());
}//void Initialise(Dictionary<string, object> InstanceDictionary)

}//class Test

public class InstanceProvider
{
/// <summary>
/// 若传入的委托不为null,则调用该委托,并传入该InstanceProvider内部的InstanceDictionary属性,从而注册所需的实例
/// </summary>
/// <param name="Initialise"></param>
public InstanceProvider(Action<Dictionary<string, object>> Initialise)
{
Initialise?.Invoke(InstanceDictionary);
}//InstanceProvider(Action<Dictionary<string, object>> Initialise)

/// <summary>
/// 这是一个键值对集合,键为string,表示的是类型名称,值为object类型,表示的是一个实例
/// </summary>
private Dictionary<string, object> InstanceDictionary { get; } = new Dictionary<string, object>();

/// <summary>
/// 根据所需类型的名称获取相应的实例
/// </summary>
/// <param name="TypeName"></param>
/// <returns></returns>
public object GetInstance(string TypeName)
{
return InstanceDictionary[TypeName];
}//object GetInstance(string TypeName)

}//class InstanceProvider

}//namespace Test

哪怕是像上面这样,在程序的最开始,将所需的类型实例全部放在一个键值对集合中,等需要时通过注册时所用的类型名称再取出来,这也完全可以说是对控制反转这一思想的一种实现。备注:当然没有哪个框架会是这么得简单,这儿旨在说明控制反转这一思想的核心。

依赖注入

什么是依赖

public class Dependency
{
private string HelloWorld { get; set; } = "Hello World";

public void WriteLine()
{
//在控制台中输出HelloWorld属性的值
Console.WriteLine(HelloWorld);
}//void WriteLine()

}//class Dependency

像上面这段代码,在Dependency中有一个HelloWorld属性,同时还有一个WriteLine方法。当我们创建出一个Dependency实例,并调用其WriteLine方法时,在控制台中输出的必然是“Hello World",绝不可能是其他。

那么问题来了,假如我要求你在不改变Dependency内部代码的情况下,调用Dependency实例中的WriteLine方法,使得控制台输出除”Hello World“以外的字符串,这又应该如何实现?(备注:也不能用反射及其他非正常的方式)

很明显不能,因为string HelloWorld属性是私有的,我们无法在Dependency外部改变该属性所对应字段的值,换句话说Dependency实例中的WriteLine方法依赖于string HelloWorld属性。这就是依赖的含义。

什么是依赖注入

依赖注入的本质就是从外部将依赖注入进对象之中。就那刚才那段代码来说,我们要向Dependency中添加哪些方法,就能改变Dependency实例中string HelloWorld属性所对应字段的值,从而使得我们在调用Dependency实例中的WriteLine方法后,控制台输出除”Hello World“以外的字符串。

方法注入
public class Dependency
{
private string HelloWorld = "Hello World";

public void ChangeHelloWorld(string NewHelloWorld)
{
HelloWorld = NewHelloWorld;
}//void ChangeHelloWorld(string NewHelloWorld)

public void WriteLine()
{
//在控制台中输出HelloWorld属性的值
Console.WriteLine(HelloWorld);
}//void WriteLine()

}//class Dependency

例如向这样,我们可以在Dependency中添加一个ChangeHelloWorld的方法,其将传入一个string NewHelloWorld,并将该NewHelloWorld赋值给Dependency内部的string HelloWorld所对应的字段,这样我们只需在调用ChangeHelloWorld的方法,改变Dependency内部的HelloWorld所对应的字段后,再调用Dependency实例中的WriteLine方法,就能实现控制台输出除”Hello World“以外的字符串。

像这种通过添加其他的方法来改变依赖的方式,我们叫做方法注入。

构造函数注入
public class Dependency
{
public Dependency(string NewHelloWorld)
{
HelloWorld = NewHelloWorld;
}//Dependency(string NewHelloWorld)

private string HelloWorld { get; set; } = "Hello World";

public void WriteLine()
{
//在控制台中输出HelloWorld字段的值
Console.WriteLine(HelloWorld);
}//void WriteLine()

}//class Dependency

除了在Dependency内部添加相应的方法来改变string HelloWorld所对应字段的值外,我们也可以在创建Dependency对象时,就传入一个string NewHelloWorld,并将该NewHelloWorld赋值给Dependency内部的string HelloWorld所对应的字段。这样我们就能在创建Dependency对象时,就改变了string HelloWorld所对应字段的值,也就能实现在我们调用WriteLine方法后,控制台输出除”Hello World“以外的字符串。

像这种通过在构造函数中传入相应对象,并改变依赖的方式,我们叫做构造函数注入。

依赖注入

像上面说的方法注入、构造函数注入,这些都是依赖注入的不同的表现形式,当然还有其他的注入方式,但是究其本质就是改变对象内部的依赖。

控制反转+依赖注入

就个人认为,控制反转和依赖注入之间并没有多大的关系。(备注:可能我的理解和别人有些不太一样)

好比是我们去麦当劳点一份双层吉士汉堡套餐(双层吉士汉堡、中份薯条、中份可乐加冰,备注:不要和我说可以改饮料和薯条),那么我们通过麦当劳收到这个套餐,这就是控制反转,即套餐是由麦当劳制作,而不是我们自己,而双层吉士汉堡套餐中需要有一个双层吉士汉堡、一个中份薯条、一个中份可乐加冰,而这些所需的材料都放进套餐中就是依赖注入。

总的来说,控制反转旨在谁去创建对象,依赖注入旨在如何创建符合要求的对象。

生命周期

对于一个框架来说,其不止要实现控制反转和依赖注入的思想,同时也要实现对所生成对象的生命周期的管理。

因为对于一个程序来说,每次获取某类型的实例时,可能需要是同一个对象,也可能需要的是新的对象,更可能的是有时候需要同一个对象有时候需要新的对象,也因此对于框架来说,一般都会将生命周期分为3种。

就以微软ASP.NET Core中所默认使用的依赖注入框架来说,其将生命周期分为了3种。(备注:一般来说,其它同类型的框架也同样分为这么几种,只不过可能名称不同)

public enum ServiceLifetime
{
Singleton,

Scoped,

Transient,
}

ServiceLifetime.Singleton表示的是单例模式,即从任意一个ServiceProvider实例中提取到的同一类型的对象都是同一对象。

ServiceLifetime.Scoped表示的是从同一个ServiceProvider实例中提取到的同一类型的对象都是同一对象。

ServiceLifetime.Transient表示的是每次从ServiceProvider实例中提取同一类型的对象都会创建出一个新的对象实例。

备注:在其框架中,ServiceProvider是一个实例对象,我们可以从其含有的GetService实例方法中提到到相应的对象。

释放资源

另外ServiceProvider还继承了IDisposable接口,我们可以通过调用其Dispose方法即可释放由该ServiceProvider实例对象所创建的对象。(感觉这么说有点不太对,不过算了,具体的要牵扯到这个框架的内部实现了,所以就不说了)

反射

虽然像Java的Spring、.Net的Autofac等框架中均使用了反射来实现依赖注入,但这并不是说要实现依赖注入就必须使用反射。

只不过对于一个框架来说,由于在静态语言中程序的运行期与编译期之间是严格分离,要想让程序在运行期干编译期的事情就必须借助于反射,因此使用反射就能大大地增加了框架/程序的灵活性,就能更好地动态生成对象。

总结

控制反转是一个框架的核心思想,依赖注入和生命周期(包括资源回收)是一个框架所实现的重要功能/需求,反射是一个框架的最重要的实现手段。

共收到 8 条回复 时间 点赞

Hi,楼主你好,非常喜欢你的帖子内容,不过希望可不可以有一些图文的方式,以及依赖注入为什么会产生的这些内容,让整个体系看起来更加完善。

小忙 回复

看了你的回复,感觉你还没懂我说的,依赖注入只是一种很简单的思想,是你把他像得太复杂了

SinDynasty 回复

😁 我整体的希望是能弄懂你的思想😁

小忙 回复

重写了下,不过还没写完

SinDynasty 回复

😻

金拱门的吉士汉堡也是我的最爱 还有就是汉堡王的皇堡,四层皇堡 谁吃谁知道

FelixKang 回复

其实我最喜欢不素之霸

SinDynasty 回复

上次吃了 芥末味。。。。酱

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up