热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【.NETCore3.0】小技巧||原生DI一对多注入

本文是一个技巧文章,内容很短,但是被提问的频率很高,所以记录下来,以待大家不时之需。以下的代码,是通过原生的依

本文是一个技巧文章,内容很短,但是被提问的频率很高,所以记录下来,以待大家不时之需。

以下的代码,是通过原生的依赖注入来讲解的,其他的第三方框架,可以自己自定义扩展,效果是一样的,那咱们先来回顾下依赖注入,都有哪几种情况。

 一、依赖注入有哪几种情况

关于依赖注入,其实我已经写了很多的文章,也录了很多的视频了,这里就不过多的解析什么了,无论是原理,还是好处,甚至是使用场景等等,如果你还不是很了解,可以看看我的视频。

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 = 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> serviceDict; public SingletonFactory() { serviceDict = new Dictionary>(); }
/// /// 根据别名,获取接口实例 /// /// /// /// public TService GetService(string id) where TService : class { // 获取接口的类型 var serviceType &#61; typeof(TService); // out 方法,先获取某一个接口下的,<别名,实例>字典 if (serviceDict.TryGetValue(serviceType, out Dictionary implDict)) { // 根据别名,获取接口的实例对象 if (implDict.TryGetValue(id, out object service)) { // 强类型转换 return service as TService; } } return null; }
/// /// 将实例和别名 匹配存储 /// /// /// /// public void AddService(TService service, string id) where TService : class { var serviceType &#61; typeof(TService); // 对象实例判空 if (service !&#61; null) { // 如果不存在,则填充 if (serviceDict.TryGetValue(serviceType, out Dictionary implDict)) { 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(new WelcomeChineseService(), "Chinese"); singletonFactory.AddService(new WelcomeEnglishService(), "English");
 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("Chinese"); moreImplServiceEnglish &#61; singletonFactory.GetService("English");}
[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 accesor &#61; key &#61;> { 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 _serviceAccessor;
public WeatherForecastController(Func serviceAccessor) { // 获取特定接口的服务访问器&#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;今天就分享到这里吧。



推荐阅读
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
author-avatar
手机用户2502929805
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有