本文是一个技巧文章,内容很短,但是被提问的频率很高,所以记录下来,以待大家不时之需。
以下的代码,是通过原生的依赖注入来讲解的,其他的第三方框架,可以自己自定义扩展,效果是一样的,那咱们先来回顾下依赖注入,都有哪几种情况。
关于依赖注入,其实我已经写了很多的文章,也录了很多的视频了,这里就不过多的解析什么了,无论是原理,还是好处,甚至是使用场景等等,如果你还不是很了解,可以看看我的视频。
https://www.bilibili.com/video/av58096866?p=5
https://www.bilibili.com/video/av73194514
上边的这个是基础和核心知识点,下边的是我直播的时候,手写的代码,可以根据自己的需要去查看。
总体来说,我一直讲的依赖注入的方式,都是面向抽象的 很常见的:一个类对应一个接口。那还有其他的注入情况么?当然还有很多,比如:
1、单独的一个类注入;
2、一个类继承了多个接口;
3、一个接口有多个实现类;
当然,除了上边这三个,还有单独类的 AOP 操作等等,一个类对应一个接口的情况,我们已经说了很多了,这里就不说了,一个类多个接口的,这个也不用说,其实就和一个类对应一个接口,是一个效果,那我们就先说说注入单独的一个类和,一个接口对应多个实现,这两种情况吧。
单独注入一个类很简单,大家都知道,依赖注入,其实就是实例化的过程,然后管理我们的对象的生命周期,降低耦合等等多个好处。
那我们既然是实例化的过程,简单啊,放到容器,直接使用它!
///
/// 1、定义一个单独类,不继承任何
///
public class OneSeparateService
{
///
/// 写一个方法,可以通过类型返回不同内容
///
///
///
public string SayHello(string type="") {
if (type == "English")
{
return "Hello";
}
else {
return "你好";
}
}
}
// 2、注入容器
services.AddScoped
///
/// 3、构造函数注入
///
///
public WeatherForecastController(OneSeparateService oneSeparateService)
{
_oneSeparateService = oneSeparateService;
}
// 4、调用
[HttpGet]
public object Get()
{
// 依赖注入,就等于下边的直接new一个实例
//OneSeparateService oneSeparateService = new OneSeparateService();
return oneSeparateService.SayHello("English");
}
我们只需要直接构造函数注入,即可使用,有种静态方法的意味,是不是很简单!当然很简单啦,因为今天我们不是说这个的,说这个仅仅是一个开胃菜,体会一下注入的过程而已。
好啦,热身完成,下面,我们就详细的说说如何实现一个接口多个实现类吧。
既然说到了一对多,那现在我们就来模式一下数据:
///
/// 1、定义一个接口
///
public interface IMoreImplService
{
string SayWelocome();
}
///
/// 2、分别定义两个实现类
///
public class WelcomeChineseService : IMoreImplService
{
public string SayWelocome()
{
return "欢迎";
}
}
public class WelcomeEnglishService : IMoreImplService
{
public string SayWelocome()
{
return "Welcome";
}
}
然后我们准备好了,该注入了,你可能会说,简单呀!直接注入然后调用不就好了:
services.AddScoped
services.AddScoped
然后直接调用
public WeatherForecastController(IMoreImplService moreImplService)
{
_moreImplService = moreImplService;
}
[HttpGet]
public object Get()
{
return _moreImplService.SayWelocome();
}
这个时候,是不是有点儿懵,嗯?那我现在到底调用的是哪个实现类呀,我们运行看看效果就知道了:
可以看到是 Welcome ,正好和我们的注入顺序是一致的,就是说,无论注入多少个,最终是最后一个生效,就好像 = new () 了好几次,把之前的实例给覆盖了一样,你应该懂了吧,不信的话,可以把注入的顺序换换,就知道啦。
请记住,刚刚我用的是 好像 字眼,真的是被覆盖掉了么,我们来看看就知道了,既然是注入了多个,我们就把多个实例都拿出来:
///
/// 1、将多个接口实例关系全部注入
///
///
public WeatherForecastController(IEnumerable
{
// 注意这里是复数
_moreImplServices = moreImplServices;
}
[HttpGet]
public object Get()
{
var result = "";
// 调用多次输出
foreach (var item in _moreImplServices)
{
result += item.SayWelocome() + "\n";
}
return result;
}
详细这个时候,你应该猜得出来答案了吧:
把两个实例都打印了出来,这就说明一个问题,我们在容器里,并不是在注入的时候,后来的把前边的给覆盖掉了,而是 本来容器里就有多个接口实例映射关系 ,只是我们在 controller 控制器里取的时候,只获取了最后一个而已!
那明白了这个问题,我们就很开心了,容器里还是都有的,我们还是可以按照我们的需要,取出想要的某一个,那我们就猜想了,如何区分呢,在文章开头,我们定义方法的时候,就是想着用一个 type ,那这里我们能不能用一个别名来做区分呢,欸,重头戏来了:
///
/// 定义一个服务工厂,Singleton注入
/// 注意这个不是真正意义上的工厂,只是提供服务的存取
///
public class SingletonFactory
{
// 定义一个字典,存储我们的接口服务和别名
Dictionary
public SingletonFactory()
{
serviceDict = new Dictionary
}
///
/// 根据别名,获取接口实例
///
///
///
///
public TService GetService
{
// 获取接口的类型
var serviceType = typeof(TService);
// out 方法,先获取某一个接口下的,<别名,实例>字典
if (serviceDict.TryGetValue(serviceType, out Dictionary
{
// 根据别名,获取接口的实例对象
if (implDict.TryGetValue(id, out object service))
{
// 强类型转换
return service as TService;
}
}
return null;
}
///
/// 将实例和别名 匹配存储
///
///
///
///
public void AddService
{
var serviceType &#61; typeof(TService);
// 对象实例判空
if (service !&#61; null)
{
// 如果不存在,则填充
if (serviceDict.TryGetValue(serviceType, out Dictionary
{
implDict[id] &#61; service;
}
else
{
implDict &#61; new Dictionary
implDict[id] &#61; service;
serviceDict[serviceType] &#61; implDict;
}
}
}
}
上边的代码相信应该能大致看的明白&#xff0c;看不明白也没关系&#xff0c;主要一句话概括&#xff0c;就是使用了一个服务&#xff0c;维护一个字典&#xff0c;利用泛型&#xff0c;先把对象实例和别名&#xff0c;分配存储到字典&#xff0c;然后再根据别名或者指定接口的对象实例&#xff0c;
就是把接口下的实现类&#xff0c;都 new 出来实例&#xff0c;然后匹配上别名&#xff0c;说白了&#xff0c;就是我们的 type&#xff0c;然后再输出出来。
那下一步&#xff0c;我们就需要先把这个单例服务给注入进去&#xff1a;
SingletonFactory singletonFactory &#61; new SingletonFactory();
singletonFactory.AddService
singletonFactory.AddService
services.AddSingleton(singletonFactory);
这个应该都能看的懂&#xff0c;唯一的小问题&#xff0c;可能会问&#xff0c;为啥要把我们的 singletonFactory 给 Singleton 注入&#xff1f;
因为我这是一个对象实例&#xff0c;只能是单例了&#xff0c;而且里边的多个服务因为已经new实例过了&#xff0c;所以没办法控制生命周期。
最后我们就来调用看看&#xff1a;
// 各自定义需要的多个字段
private readonly IMoreImplService moreImplServiceChinese;
private readonly IMoreImplService moreImplServiceEnglish;
public WeatherForecastController(SingletonFactory singletonFactory)
{
this.singletonFactory &#61; singletonFactory;
// 根据别名获取服务
moreImplServiceChinese &#61; singletonFactory.GetService
moreImplServiceEnglish &#61; singletonFactory.GetService
}
[HttpGet("/welcome")]
public object GetWelcome()
{
return moreImplServiceChinese.SayWelocome();
}
结果我们不用看了&#xff0c;已经成功了&#xff0c;最后我们再来回顾一下这种写法的步骤&#xff1a;
1、定义一个单例服务类&#xff0c;将我们的多个对象new出来实例&#xff0c;和别名对应存储起来&#xff1b;
2、将单例类实例化后&#xff0c;注入服务容器&#xff1b;
3、控制器获取单例类&#xff0c;并根据别名获取相对应的服务&#xff1b;
到了这里&#xff0c;我们就已经完成了&#xff0c;是不是到了这里&#xff0c;感觉是已经完成了&#xff0c;但是又感觉哪里不是很舒服&#xff0c;比如这样注入的实例都是单例的&#xff0c;那这样不是很合适呀&#xff1f;
如何才能适应不同的生命周期呢&#xff0c;我这里提供第二个方法&#xff1a;
// 先把多个实现类服务注入进去
services.AddScoped
services.AddScoped
// 然后通过简单工厂模式&#xff0c;针对不同的 key 来获取指定的对象实例
services.AddScoped(factory &#61;>
{
Func
{
if (key.Equals("Chinese"))
{
// 因为这里是从容器获取服务实例的&#xff0c;所以我们可以控制生命周期
return factory.GetService
}
else if (key.Equals("English"))
{
return factory.GetService
}
else
{
throw new ArgumentException($"Not Support key : {key}");
}
};
return accesor;
});
大家可以看一下&#xff0c;我们用的是 Scope 方式注入的&#xff0c;三种生命周期都可以&#xff0c;接下看就是调用了&#xff1a;
// 将我们的规则 Func 构造函数注入
private readonly Func
public WeatherForecastController(Func
{
// 获取特定接口的服务访问器&#xff0c;然后根据别名获取
_serviceAccessor &#61; serviceAccessor;
// 这里的别名&#xff0c;你可以配置到 appsetting.json 文件里&#xff0c;动态的修改获取对象实例
// 然后再在接口中配置一个字段 string ImplementKeyName { get; }
moreImplServiceChinese &#61; _serviceAccessor("Chinese");
moreImplServiceEnglish &#61; _serviceAccessor("English");
}
[HttpGet("/welcome")]
public object GetWelcome()
{
return moreImplServiceChinese.SayWelocome() &#43; "\n" &#43; moreImplServiceEnglish.SayWelocome();
}
为了演示效果&#xff0c;我把Service服务的构造函数&#xff0c;增加一个动态时间&#xff0c;来判断是否满足Scope需求&#xff0c;那现在我们就来看看效果吧&#xff1a;
public class WelcomeChineseService : IMoreImplService
{
public DateTime Now { get; set; }
public WelcomeChineseService()
{
Now &#61; DateTime.Now;
}
public string SayWelocome()
{
return "欢迎" &#43; Now;
}
}
好啦&#xff0c;最后我们来总结一下这个方法的优点&#xff1a;
1、可以实现一个接口对应多个实现类的注入和获取&#xff1b;
2、实例别名可以配置到 appsettings.json 里&#xff0c;动态获取指定服务&#xff1b;
3、可以指定生命周期&#xff1b;
4、更直观&#xff0c;更简单&#xff1b;
虽然这种简单工厂的写法&#xff0c;还是不够优雅&#xff0c;但是毕竟这种一个接口多个实现类的方法本身就不是很优雅&#xff0c;好啦&#xff0c;今天就分享到这里吧。