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

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


当前回答

让我们用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的简短定义。

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

其他回答

“依赖注入”不就是指使用参数化构造函数和公共setter吗?

James Shore的文章展示了以下示例进行比较。

没有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;public Example(){myDatabase=新数据库Thingie();} public void doStuff(){... myDatabase.getData();... } } 具有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;公共示例(DatabaseThingie使用ThisDatabaseReplace){myDatabase=改用此数据库;}public void doStuff(){... myDatabase.getData();... } }

例如,我们有两类客户机和服务。客户端将使用服务

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

无依赖注入

方式1)

public class Client {
    public void doSomeThingInClient() {
        Service service = new Service();
        service.doSomeThingInService();
    }
}

方式2)

public class Client {
    Service service = new Service();
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

方式3)

public class Client {
    Service service;
    public Client() {
        service = new Service();
    }
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

1) 2)3)使用

Client client = new Client();
client.doSomeThingInService();

优势

易于理解的

缺点

难以测试客户端类当我们更改Service构造函数时,我们需要在所有位置更改代码createService对象

使用依赖注入

方式1)构造函数注入

public class Client {
    Service service;

    Client(Service service) {
        this.service = service;
    }

    // Example Client has 2 dependency 
    // Client(Service service, IDatabas database) {
    //    this.service = service;
    //    this.database = database;
    // }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用

Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();

方式2)沉淀剂注入

public class Client {
    Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用

Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();

方式3)接口注入

检查https://en.wikipedia.org/wiki/Dependency_injection

===

现在,这段代码已经遵循了依赖注入,测试客户端类更容易。然而,我们仍然多次使用新的Service(),并且在更改Service构造函数时效果不佳。为了防止这种情况,我们可以使用DI注射器1) 简单手动喷油器

public class Injector {
    public static Service provideService(){
        return new Service();
    }

    public static IDatabase provideDatatBase(){
        return new SqliteDatabase();
    }
    public static ObjectA provideObjectA(){
        return new ObjectA(provideService(...));
    }
}

使用

Service service = Injector.provideService();

2) 使用库:适用于Android dagger2

优势

使测试更容易更改服务时,只需在Injector类中更改如果您使用使用构造函数注入,当您查看Client的构造函数时,您将看到Client类有多少依赖项

缺点

如果使用构造函数注入,则在创建客户端时创建服务对象,有时我们在客户端类中使用函数而不使用服务,因此创建的服务被浪费

依赖注入定义

https://en.wikipedia.org/wiki/Dependency_injection

依赖项是可以使用的对象(服务)注入是将依赖项(Service)传递给将使用它的依赖对象(Client)

我知道已经有很多答案,但我发现这非常有用:http://tutorials.jenkov.com/dependency-injection/index.html

无相关性:

public class MyDao {

  protected DataSource dataSource = new DataSourceImpl(
    "driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}     
}

附属国:

public class MyDao {

  protected DataSource dataSource = null;

  public MyDao(String driver, String url, String user, String password) {
    this.dataSource = new DataSourceImpl(driver, url, user, password);
  }

  //data access methods...
  public Person readPerson(int primaryKey) {...}
}

注意DataSourceImpl实例化是如何移动到构造函数中的。构造函数接受四个参数,即DataSourceImpl所需的四个值。虽然MyDao类仍然依赖于这四个值,但它本身不再满足这些依赖关系。它们由创建MyDao实例的任何类提供。

使依赖注入概念易于理解。让我们以开关按钮为例来切换(打开/关闭)灯泡。

无依赖注入

Switch需要事先知道我连接到哪个灯泡(硬编码依赖项)。所以

开关->永久灯泡//开关直接连接到永久灯泡,测试不容易

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

使用依赖注入

开关只知道我需要打开/关闭传递给我的灯泡。所以,

开关->灯泡1或灯泡2或夜灯泡(注入依赖性)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

修改开关和灯泡的James示例:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

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

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

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

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

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

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

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

.