已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,

什么是依赖注入,何时/为什么应该或不应该使用它?


当前回答

依赖注入是一种实践,它使解耦的组件与它们的一些依赖不可知,这遵循SOLID准则

依赖反转原则:一个人应该“依赖于抽象,而不是结核。

依赖注入的更好实现是Composition Root设计模式,因为它允许组件与依赖注入容器分离。

我再次推荐这篇关于作文根的伟大文章http://blog.ploeh.dk/2011/07/28/CompositionRoot/作者:Mark Seemann

本文的要点如下:

合成根是应用程序中的(最好)唯一位置其中模块被组合在一起。

...

只有应用程序应该具有合成根。图书馆和框架不应该。

...

DI容器只能从合成根引用。所有其他模块都不应引用容器。

Di Ninja(依赖注入框架)的文档是一个很好的例子,可以演示组合根和依赖注入的原理是如何工作的。https://github.com/di-ninja/di-ninja正如我所知,是javascript中唯一实现Composition Root设计模式的DiC。

其他回答

依赖注入(DI)是设计模式中的一种,它使用了OOP的基本特性——一个对象与另一个对象之间的关系。虽然继承继承一个对象以实现更复杂和更具体的另一个对象,但关系或关联只需使用属性从一个对象创建指向另一对象的指针。DI的功能与OOP的其他特性相结合,如接口和隐藏代码。假设图书馆里有一个客户(订阅者),为了简单起见,他只能借一本书。

书本界面:

package com.deepam.hidden;

public interface BookInterface {

public BookInterface setHeight(int height);
public BookInterface setPages(int pages);   
public int getHeight();
public int getPages();  

public String toString();
}

接下来我们可以有很多种书;其中一种类型是虚构:

package com.deepam.hidden;

public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages

/** constructor */
public FictionBook() {
    // TODO Auto-generated constructor stub
}

@Override
public FictionBook setHeight(int height) {
  this.height = height;
  return this;
}

@Override
public FictionBook setPages(int pages) {
  this.pages = pages;
  return this;      
}

@Override
public int getHeight() {
    // TODO Auto-generated method stub
    return height;
}

@Override
public int getPages() {
    // TODO Auto-generated method stub
    return pages;
}

@Override
public String toString(){
    return ("height: " + height + ", " + "pages: " + pages);
}
}

现在,用户可以与图书建立关联:

package com.deepam.hidden;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Subscriber {
BookInterface book;

/** constructor*/
public Subscriber() {
    // TODO Auto-generated constructor stub
}

// injection I
public void setBook(BookInterface book) {
    this.book = book;
}

// injection II
public BookInterface setBook(String bookName) {
    try {
        Class<?> cl = Class.forName(bookName);
        Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
        BookInterface book = (BookInterface) constructor.newInstance();
        //book = (BookInterface) Class.forName(bookName).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return book;
}

public BookInterface getBook() {
  return book;
}

public static void main(String[] args) {

}

}

这三个类都可以隐藏起来,以便实现自己的功能。现在我们可以将此代码用于DI:

package com.deepam.implement;

import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;

public class CallHiddenImplBook {

public CallHiddenImplBook() {
    // TODO Auto-generated constructor stub
}

public void doIt() {
    Subscriber ab = new Subscriber();

    // injection I
    FictionBook bookI = new FictionBook();
    bookI.setHeight(30); // cm
    bookI.setPages(250);
    ab.setBook(bookI); // inject
    System.out.println("injection I " + ab.getBook().toString());

    // injection II
    FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
    System.out.println("injection II " + ab.getBook().toString());      
}

public static void main(String[] args) {
    CallHiddenImplBook kh = new CallHiddenImplBook();
    kh.doIt();
}
}

如何使用依赖注入有许多不同的方法。可以将它与Singleton等结合起来,但基本上它只是通过在另一个对象内创建对象类型的属性来实现的关联。它的有用性是唯一的,也是唯一的特点,我们应该反复编写的代码总是为我们准备好并做好准备。这就是为什么DI如此紧密地与控制反转(IoC)绑定,这意味着我们的程序将控制传递给另一个正在运行的模块,该模块将bean注入到我们的代码中。(可以被注入的每个对象都可以被签名或被认为是一个Bean。)例如,在Spring中,它是通过创建和初始化ApplicationContext容器来完成的,这对我们来说很有用。我们只需在代码中创建Context并调用初始化Bean。此时注射已自动完成。

让我们用Car和Engine类来尝试一个简单的例子,任何汽车都需要一个引擎,至少目前是这样。下面是没有依赖注入的代码的外观。

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

为了实例化Car类,我们将使用以下代码:

Car car = new Car();

这个代码的问题是我们与GasEngine紧密耦合,如果我们决定将其更改为ElectricityEngine,那么我们需要重写Car类。应用程序越大,我们必须添加和使用新型引擎的问题和麻烦就越多。

换句话说,这种方法是我们的高级Car类依赖于低级GasEngine类,这违反了SOLID的依赖反转原理(DIP)。DIP建议我们应该依赖抽象,而不是具体的类。因此,为了满足这一点,我们引入了IEngine接口并重写如下代码:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

现在我们的Car类只依赖于IEngine接口,而不是引擎的特定实现。现在,唯一的诀窍是我们如何创建Car的实例,并给它一个实际的具体Engine类,比如GasEngine或ElectricityEngine。这就是依赖注入的作用。

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

在这里,我们基本上将依赖项(Engine实例)注入(传递)到Car构造函数。因此,现在我们的类在对象及其依赖关系之间具有松散的耦合,我们可以在不更改Car类的情况下轻松添加新类型的引擎。

依赖注入的主要好处是类之间的耦合更加松散,因为它们没有硬编码的依赖关系。这遵循了上面提到的依赖反转原则。类不是引用特定的实现,而是请求抽象(通常是接口),这些抽象是在构造类时提供给它们的。

所以最终依赖注入只是一种技术实现对象及其依赖关系之间的松散耦合。而不是直接实例化类需要的依赖项为了执行其操作,向类提供依赖项(通常)通过构造函数注入。

此外,当我们有很多依赖项时,使用控制反转(IoC)容器是非常好的做法,我们可以告诉哪些接口应该映射到我们所有依赖项的具体实现,我们可以让它在构建对象时为我们解决这些依赖项。例如,我们可以在IoC容器的映射中指定IEngine依赖项应映射到GasEngine类,当我们向IoC容器请求Car类的实例时,它将自动构建Car类,并传入GasEngine依赖项。

更新:最近观看了Julie Lerman关于EF Core的课程,也喜欢她对DI的简短定义。

依赖注入是一种允许应用程序注入的模式对象,而无需强制类来负责这些对象。它允许您的代码更松散的耦合,并且实体框架核心插入到该框架中服务体系。

在进行技术描述之前,首先用一个真实的例子来形象化它,因为你会发现很多技术知识需要学习依赖注入,但大多数人都无法理解它的核心概念。

在第一张图中,假设你有一家拥有很多单位的汽车工厂。汽车实际上是在装配单元中制造的,但它需要发动机、座椅和车轮。因此,装配单元依赖于这些所有单元,它们是工厂的依赖。

你可以感觉到,现在在这个工厂维护所有的任务太复杂了,因为除了主要任务(在组装单元组装汽车)外,你还必须关注其他单元。现在维护成本很高,而且厂房很大,因此需要额外的租金。

现在,看第二张图。如果你找到一些供应商公司,他们会以比你自己生产成本更低的价格为你提供车轮、座椅和发动机,那么现在你就不需要在工厂里生产了。您现在可以为您的装配单元租用一栋较小的建筑,这将减少您的维护任务,并降低额外的租赁成本。现在你也可以只专注于你的主要任务(汽车组装)。

现在我们可以说,组装汽车的所有依赖都是由供应商注入工厂的。这是一个真实的依赖注入(DI)示例。

现在用技术术语来说,依赖注入是一种技术,一个对象(或静态方法)提供另一个对象的依赖。因此,将创建对象的任务传递给其他人并直接使用依赖关系称为依赖注入。

这将帮助您现在通过技术说明学习DI。这将显示何时使用DI,何时不使用DI。

.

让我们想象一下,你想去钓鱼:

没有依赖注入,你需要自己处理所有的事情。你需要找一艘船,买一根鱼竿,寻找诱饵等等。当然,这是可能的,但这给你带来了很多责任。在软件方面,这意味着你必须对所有这些东西进行查找。通过依赖注入,其他人负责所有准备工作,并为您提供所需的设备。你将收到(“被注射”)船、鱼竿和鱼饵——所有这些都准备好使用。

我能想到的最好的类比是手术室中的外科医生和他的助手,在那里,外科医生是主要的人,他的助手在他需要时提供各种手术组件,以便外科医生能够专注于他最擅长的一件事(手术)。如果没有助手,外科医生每次需要时都必须自己取下部件。

简而言之,DI是一种通过向组件提供依赖组件来消除组件获取依赖组件的常见额外责任(负担)的技术。

DI使您更接近单一责任(SR)原则,就像外科医生可以专注于外科手术一样。

何时使用DI:我建议在几乎所有的生产项目(小型/大型)中使用DI,尤其是在不断变化的业务环境中:)

原因:因为您希望代码易于测试、可模拟等,以便快速测试更改并将其推向市场。此外,当你有很多很棒的免费工具/框架来支持你的代码库之旅时,你为什么不这样做呢。