我试图理解依赖注入(DI),但又一次失败了。这看起来很傻。我的代码从来不是一团糟;我几乎不编写虚函数和接口(尽管我很少写一次),而且我的所有配置都神奇地使用json.net序列化成一个类(有时使用XML序列化器)。

我不太明白它能解决什么问题。它看起来像是在说:“嗨。当你遇到这个函数时,返回一个该类型的对象,并使用这些参数/数据。” 但是…我为什么要用这个?注意,我从来没有需要使用object,但我知道这是为了什么。

在构建网站或桌面应用程序时,使用DI的真实情况是什么?我可以很容易地想出为什么有人想要在游戏中使用界面/虚拟函数的例子,但在非游戏代码中使用这种方法的情况非常罕见(我想不起一个例子)。


当前回答

我认为经典的答案是创建一个更加解耦的应用程序,它不知道在运行时将使用哪个实现。

例如,我们是一家中央支付提供商,与世界各地的许多支付提供商合作。但是,当发出请求时,我不知道要调用哪个支付处理器。我可以用大量的开关情况编写一个类,比如:

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Now imagine that now you'll need to maintain all this code in a single class because it's not decoupled properly, you can imagine that for every new processor you'll support, you'll need to create a new if // switch case for every method, this only gets more complicated, however, by using Dependency Injection (or Inversion of Control - as it's sometimes called, meaning that whoever controls the running of the program is known only at runtime, and not complication), you could achieve something very neat and maintainable.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

**代码不会编译,我知道:)

其他回答

正如其他答案所述,依赖注入是在使用它的类之外创建依赖项的一种方式。您从外部注入它们,并从类内部控制它们的创建。这也是为什么依赖注入是控制反转(IoC)原则的实现。

IoC是原则,DI是模式。根据我的经验,您可能“需要多个记录器”的原因实际上从未得到满足,但实际的原因是,无论何时测试某个东西,您都确实需要它。一个例子:

我的特点:

当我看报价时,我想要标记我是自动看的,这样我就不会忘记这么做。

你可以这样测试:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

在OfferWeasel的某处,它像这样为你创建了一个offer对象:

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

这里的问题是,这个测试很可能总是失败,因为所设置的日期将不同于所断言的日期,即使您只输入DateTime。现在在测试代码中,它可能会关闭几毫秒,因此总是会失败。现在一个更好的解决方案是创建一个接口,允许你控制将设置的时间:

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

接口是抽象。一个是真实的东西,另一个可以让你在需要的时候假装。测试可以这样更改:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

Like this, you applied the "inversion of control" principle, by injecting a dependency (getting the current time). The main reason to do this is for easier isolated unit testing, there are other ways of doing it. For example, an interface and a class here is unnecessary since in C# functions can be passed around as variables, so instead of an interface you could use a Func<DateTime> to achieve the same. Or, if you take a dynamic approach, you just pass any object that has the equivalent method (duck typing), and you don't need an interface at all.

您几乎不需要超过一个记录器。尽管如此,依赖注入对于静态类型的代码(如Java或c#)是必不可少的。

和… 还应该注意的是,如果对象的所有依赖项都可用,则对象只能在运行时正确地实现其目的,因此设置属性注入没有太大用处。在我看来,当调用构造函数时,所有的依赖关系都应该得到满足,所以构造函数注入是可以使用的。

坦率地说,我相信人们使用这些依赖注入库/框架是因为他们只知道如何在运行时做事情,而不是在加载时。所有这些疯狂的机制都可以通过设置CLASSPATH环境变量(或其他语言等效变量,如PYTHONPATH、LD_LIBRARY_PATH)来替代,以指向特定类的替代实现(都具有相同的名称)。所以在接受的答案中,你只需要留下你的代码

var logger = new logger() //简单的代码

适当的记录器将被实例化,因为JVM(或其他运行时或。so加载器)将从通过上面提到的环境变量配置的类中获取它。

不需要把所有东西都变成一个接口,不需要疯狂地生成破碎的对象,把东西注入其中,不需要疯狂地构造函数,把内部机制的每一块都暴露给世界。只要使用你所使用的任何语言的本地功能,而不是想出在任何其他项目中都不起作用的方言。

注:这同样适用于测试/模拟。您可以很好地设置您的环境,在加载时加载适当的模拟类,而跳过模拟框架的疯狂。

使用DI的主要原因是,您希望将实现知识的责任放在知识所在的位置。依赖注入的思想非常符合封装和按接口设计。 如果前端向后端请求一些数据,那么后端如何解决这个问题对于前端来说并不重要。这取决于请求处理程序。

这在OOP中已经很常见了。很多时候创建如下代码段:

I_Dosomething x = new Impl_Dosomething();

缺点是实现类仍然是硬编码的,因此前端有使用哪个实现的知识。DI将接口设计进一步推进了一步,前端需要知道的唯一一件事就是接口的知识。 在DYI和DI之间是服务定位器的模式,因为前端必须提供一个键(出现在服务定位器的注册表中)来解析它的请求。 服务定位器示例:

I_Dosomething x = ServiceLocator.returnDoing(String pKey);

在操作:

I_Dosomething x = DIContainer.returnThat();

DI的要求之一是容器必须能够找出哪个类是哪个接口的实现。因此,DI容器需要强类型设计,并且每个接口同时只能有一个实现。如果同时需要一个接口的多个实现(如计算器),则需要服务定位器或工厂设计模式。

D(b)I:依赖注入和接口设计。 不过,这个限制并不是一个非常大的实际问题。使用D(b)I的好处是它为客户端和提供者之间的通信提供服务。接口是一个对象或一组行为的视角。后者在这里至关重要。

I prefer the administration of service contracts together with D(b)I in coding. They should go together. The use of D(b)I as a technical solution without organizational administration of service contracts is not very beneficial in my point of view, because DI is then just an extra layer of encapsulation. But when you can use it together with organizational administration you can really make use of the organizing principle D(b)I offers. It can help you in the long run to structure communication with the client and other technical departments in topics as testing, versioning and the development of alternatives. When you have an implicit interface as in a hardcoded class, then is it much less communicable over time then when you make it explicit using D(b)I. It all boils down to maintenance, which is over time and not at a time. :-)

我认为经典的答案是创建一个更加解耦的应用程序,它不知道在运行时将使用哪个实现。

例如,我们是一家中央支付提供商,与世界各地的许多支付提供商合作。但是,当发出请求时,我不知道要调用哪个支付处理器。我可以用大量的开关情况编写一个类,比如:

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Now imagine that now you'll need to maintain all this code in a single class because it's not decoupled properly, you can imagine that for every new processor you'll support, you'll need to create a new if // switch case for every method, this only gets more complicated, however, by using Dependency Injection (or Inversion of Control - as it's sometimes called, meaning that whoever controls the running of the program is known only at runtime, and not complication), you could achieve something very neat and maintainable.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

**代码不会编译,我知道:)

我认为很多时候人们会混淆依赖注入和依赖注入框架(或者通常被称为容器)之间的区别。

依赖注入是一个非常简单的概念。而不是下面的代码:

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

你可以这样写代码:

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

就是这样。认真对待。这给了你很多好处。两个重要的特性是能够从一个中心位置(Main()函数)控制功能,而不是将其分散到整个程序中,以及能够更容易地单独测试每个类(因为可以将模拟对象或其他伪造对象传递到其构造函数中,而不是传递实际值)。

当然,缺点是现在只有一个超级函数,它知道程序使用的所有类。这就是依赖注入框架可以提供的帮助。但是,如果您无法理解为什么这种方法有价值,我建议您先从手动依赖注入开始,这样您就可以更好地了解各种框架可以为您做些什么。