为什么依赖注入出现的频率这么高?

前言

早上坐地铁,脑子里突然闪到依赖注入这个词,然后努力回忆一下,觉得似懂非懂的,很模糊。
依赖注入在日常项目里出现的频率真的是很高了,不管是.net core,还是angular都有使用,那么依赖注入究竟有什么优势呢?

一、什么是依赖注入

依赖注入(Dependency Injection)是用于实现控制反转(Inversion Of Control)的最常见的方式之一。
那么什么是控制反转?

控制反转
是面向对象编程中的一种设计原则,常用于解耦。因为大多数应用程序都是由两个或是更多的类通过彼此的合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现(也就是通过new一个你要使用的对象),那么这将导致代码高度耦合并且难以维护和调试。而控制反转就是为了解决这个问题。
简单来说:获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入

接着来说依赖注入
依赖注入(DI)不需要直接new来获得这个对象,而是通过相关的容器控制程序,来将要使用的对象在外部new出来并注入到A类里的引用中,具体获取的方法和对象被获取时的状态则由配置文件(如XML)来指定。
简单的来说就是依赖注入提供了装配能力,框架负责new对象,以及把他们组织起来,你唯一需要做的就是调用业务方法。
再翻译成大白话来说,如果你想吃番茄炒蛋,如果在家自己做的话,需要切番茄,打鸡蛋,起锅放油炒等等等步骤很麻烦,那我可以直接去找个餐厅,点一份番茄炒蛋,我不管你是怎么做的,直接吃就行了。
于是:

  • 控制反转就是你要吃番茄炒蛋,但是又觉得麻烦不想做的解决方案。
  • 依赖注入其实就是你去餐厅点菜这个实现方式,也是解决方案的具体实现。
  • 番茄炒蛋就是你要用的对象。
  • 番茄炒蛋的配方就是你的配置文件。

最后做个总结:
控制反转(IOC)是实现解耦的一种解决方案,依赖注入是控制反转的一种实现方式,它提供了装配能力,框架负责new对象并且管理他们,当你要使用的时候告诉他要使用什么就可以了(听起来很像工厂模式?)
试着描述一下这个过程:
对象A依赖于对象B,当对象 A需要用到对象B的时候,IOC容器就会立即创建一个对象B送给对象A。IOC容器就是一个对象制造工厂,你需要什么,它会给你送去,你直接使用就行了,而再也不用去关心你所用的东西是如何制成的,也不用关心最后是怎么被销毁的,这一切全部由IOC容器包办。

二、依赖注入的优缺点

与其说是依赖注入的优缺点,不如说是控制反转IOC的优缺点。
先说优点,拿上面那个番茄炒蛋的例子来说:
在这里插入图片描述

第一、番茄炒蛋作为被吃的菜而言,在顾客点菜之前,与顾客没有任何的关系,只有顾客点了番茄炒蛋,两者才发生联系,具有相关性。所以,无论两者中的任何一方出现什么的问题,都不会影响另一方这种特性体现在软件工程中,就是可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。
代码中的每一个Class都可以单独测试,彼此之间互不影响,只要保证自身的功能无误即可,这就是组件之间低耦合或者无耦合带来的好处。
第二、番茄炒蛋和顾客的之间无关性,还带来了另外一个好处,做菜的餐厅和顾客完全可以是互不相干的,他们之间唯一需要遵守的就是菜品质量标准,
这种特性体现在软件开发过程中,每个开发团队的成员都只需要关心实现自身的业务逻辑,完全不用去关心其它的人工作进展,因为你的任务跟别人没有任何关系,也不用依赖于别人的组件,可以单独测试,再也不用扯不清责任了。
在一个中大型项目中,团队成员分工明确、责任明晰,很容易将一个大的任务划分为细小的任务,开发效率和产品质量必将得到大幅度的提高。
第三、餐厅可以把番茄炒蛋可以做给任何喜欢吃的人,可以给小红,小黄,小蓝,等等等。
在软件工程中,这种特性就是可复用性好,我们可以把具有普遍性的常用组件独立出来,反复利用到项目中的其它部分,或者是其它项目,当然这也是面向对象的基本特征。显然,IOC不仅更好地贯彻了这个原则,提高了模块的可复用性。符合接口标准的实现,都可以插接到支持此标准的模块中。
第四、如果有一天有顾客说,我喜欢吃偏甜的番茄炒蛋,那么只用在做番茄鸡蛋的时候多放糖就行了,改下菜的配方。
在IOC中只要修改配置文件就可以了,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。

缺点
第一、软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
第二、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
第三、具体到IOC框架产品来讲,需要进行大量的配制工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。(对于使用IDE重构,如修改类名的时候,XML文件中的类名必须手动修改)
第四、IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。

总结:
关于优缺点部分参考自:传送门
我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品。

三、依赖注入的几种方式,及优缺点

1、构造函数注入,最常用的注入方式,也是angular和.net core默认的注入方式,(框架内部已经帮你实现了IOC容器)

// 代码来自.Net Core 官网关于依赖注入的教程
public class MyDependency : IMyDependency
{
    
    
    private readonly ILogger<MyDependency> _logger;

    public MyDependency(ILogger<MyDependency> logger)
    {
    
    
        _logger = logger;
    }

    public Task WriteMessage(string message)
    {
    
    
        _logger.LogInformation(
            "MyDependency.WriteMessage called. Message: {Message}", 
            message);

        return Task.FromResult(0);
    }
}
// 使用的前提,必须在Startup.ConfigureServices容器中注册服务,具体不做描述
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
// 代码来自Angular官网关于依赖注入的教程
export class UserContextService {
    
    
  constructor(private userService: UserService, private loggerService: LoggerService) {
    
    
  }
}
// 使用的前提,必须在UserService添加@Injectable() 装饰器,下面代码意思是把此服务注入到根module中
@Injectable({
    
    
  providedIn: 'root'
})

优点:

  • 在构造方法中体现出对其他类的依赖,一眼就能看出这个类需要其他那些类才能工作。
  • 脱离了IOC框架,这个类仍然可以工作。
  • 一旦对象初始化成功了,这个对象的状态肯定是正确的。

缺点:

  • 构造函数会有很多参数。
  • 有些类是需要默认构造函数的,比如MVC框架的Controller类,一旦使用构造函数注入,就无法使用默认构造函数。
  • 这个类里面的有些方法并不需要用到这些依赖。

2、Setter注入
通过属性的访问器进行依赖注入

private IMyInterFace _myInterface; 
public IMyInterFace myInterface
{
    
     
    get {
    
     return _myInterface; } 
    set {
    
     _myInterface = value; } 
} 

优点:

  • 在对象的整个生命周期内,可以随时动态的改变依赖,非常灵活。

缺点:

  • 对象在创建后,被设置依赖对象之前这段时间状态是不对的。
  • 相比构造函数注入,显得不直观,无法清晰地表示哪些属性是必须的。
    3、接口注入,通过接口实现依赖注入
public interface IMyInterFace
{
    
    
    IMyInterFace myInterface {
    
     get; set; }
}

class MyClass : IMyInterFace
{
    
    
    private IMyInterFace _myInterface;
    public IMyInterFace myInterfacce
    {
    
    
        get {
    
     return _myInterface; }
        set {
    
     _myInterface= value; }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41885871/article/details/105946478