天天看點

為什麼使用依賴注入?

本文翻譯自:Why does one use dependency injection?

I'm trying to understand dependency injections (DI), and once again I failed.

我正在嘗試了解依賴注射 (DI),并再一次失敗了。

It just seems silly.

這看起來很傻。

My code is never a mess;

我的代碼從來都不是一團糟;

I hardly write virtual functions and interfaces (although I do once in a blue moon) and all my configuration is magically serialized into a class using json.net (sometimes using an XML serializer).

我幾乎沒有編寫虛函數和接口(雖然我曾經在藍月亮中做過)并且我的所有配置都被神奇地序列化為使用json.net的類(有時使用XML序列化器)。

I don't quite understand what problem it solves.

我不太明白它解決了什麼問題。

It looks like a way to say: "hi. When you run into this function, return an object that is of this type and uses these parameters/data."

它看起來像是一種說法:“嗨。當你遇到這個函數時,傳回一個這種類型的對象并使用這些參數/資料。”

But... why would I ever use that?

但是......為什麼我會用它呢?

Note I have never needed to use

object

as well, but I understand what that is for.

注意我從來不需要使用

object

,但我明白這是什麼。

What are some real situations in either building a website or desktop application where one would use DI?

在建構網站或桌面應用程式時,哪些人會使用DI?

I can come up with cases easily for why someone may want to use interfaces/virtual functions in a game, but it's extremely rare (rare enough that I can't remember a single instance) to use that in non-game code.

我可以輕松地提出案例,為什麼有人可能想在遊戲中使用接口/虛拟功能,但在非遊戲代碼中使用它非常罕見(很少見,我記不起單個執行個體)。

#1樓

參考:https://stackoom.com/question/y0RZ/為什麼使用依賴注入

#2樓

First, I want to explain an assumption that I make for this answer.

首先,我想解釋一下我為這個答案做出的假設。

It is not always true, but quite often:

它并不總是如此,但經常是:
Interfaces are adjectives; 界面是形容詞; classes are nouns. 課程是名詞。

(Actually, there are interfaces that are nouns as well, but I want to generalize here.)

(實際上,有些名詞也是名詞,但我想在這裡概括一下。)

So, eg an interface may be something such as

IDisposable

,

IEnumerable

or

IPrintable

.

是以,例如,接口可以是諸如

IDisposable

IEnumerable

IPrintable

A class is an actual implementation of one or more of these interfaces:

List

or

Map

may both be implementations of

IEnumerable

.

類是這些接口中的一個或多個的實際實作:

List

Map

都可以是

IEnumerable

實作。

To get the point: Often your classes depend on each other.

要明白這一點:通常你的課程互相依賴。

Eg you could have a

Database

class which accesses your database (hah, surprise! ;-)), but you also want this class to do logging about accessing the database.

例如,您可以擁有一個通路資料庫的

Database

類(hah,surprise!;-)),但您也希望此類執行有關通路資料庫的日志記錄。

Suppose you have another class

Logger

, then

Database

has a dependency to

Logger

.

假設您有另一個類

Logger

,那麼

Database

Logger

有依賴關系。

So far, so good.

到現在為止還挺好。

You can model this dependency inside your

Database

class with the following line:

您可以使用以下行在

Database

類中對此依賴項進行模組化:
var logger = new Logger();
           

and everything is fine.

一切都很好。

It is fine up to the day when you realize that you need a bunch of loggers: Sometimes you want to log to the console, sometimes to the file system, sometimes using TCP/IP and a remote logging server, and so on ...

當你意識到你需要一堆記錄器時,這是很好的:有時你想要登入到控制台,有時你想登入到檔案系統,有時候使用TCP / IP和遠端登入伺服器,等等......

And of course you do NOT want to change all your code (meanwhile you have gazillions of it) and replace all lines

當然,你不想改變所有的代碼(同時你擁有它gazillions)和替換所有行
var logger = new Logger();
           

by:

通過:
var logger = new TcpLogger();
           

First, this is no fun.

首先,這不好玩。

Second, this is error-prone.

其次,這容易出錯。

Third, this is stupid, repetitive work for a trained monkey.

第三,對于訓練有素的猴子來說,這是一項愚蠢的,重複性的工作。

So what do you do?

是以你會怎麼做?

Obviously it's a quite good idea to introduce an interface

ICanLog

(or similar) that is implemented by all the various loggers.

顯然,引入由所有各種記錄器實作的接口

ICanLog

(或類似物)是一個相當不錯的主意。

So step 1 in your code is that you do:

是以,代碼中的第1步是:
ICanLog logger = new Logger();
           

Now the type inference doesn't change type any more, you always have one single interface to develop against.

現在類型推斷不再改變類型,你總是有一個單獨的接口來開發。

The next step is that you do not want to have

new Logger()

over and over again.

下一步是您不希望一遍又一遍地使用

new Logger()

So you put the reliability to create new instances to a single, central factory class, and you get code such as:

是以,您可以為單個中央工廠類建立新執行個體,并獲得以下代碼:
ICanLog logger = LoggerFactory.Create();
           

The factory itself decides what kind of logger to create.

工廠本身決定要建立哪種記錄器。

Your code doesn't care any longer, and if you want to change the type of logger being used, you change it once : Inside the factory.

您的代碼不再關心,如果您想更改正在使用的記錄器類型,您可以更改一次 :在工廠内部。

Now, of course, you can generalize this factory, and make it work for any type:

當然,現在您可以概括這個工廠,并使其适用于任何類型:
ICanLog logger = TypeFactory.Create<ICanLog>();
           

Somewhere this TypeFactory needs configuration data which actual class to instantiate when a specific interface type is requested, so you need a mapping.

在某個地方,這個TypeFactory需要配置資料,當請求特定的接口類型時,實際的類要執行個體化,是以你需要一個映射。

Of course you can do this mapping inside your code, but then a type change means recompiling.

當然,您可以在代碼中進行此映射,但是類型更改意味着重新編譯。

But you could also put this mapping inside an XML file, eg.

但您也可以将此映射放在XML檔案中,例如。

This allows you to change the actually used class even after compile time (!), that means dynamically, without recompiling!

這允許您甚至在編譯時(!)之後更改實際使用的類,這意味着動态地,無需重新編譯!

To give you a useful example for this: Think of a software that does not log normally, but when your customer calls and asks for help because he has a problem, all you send to him is an updated XML config file, and now he has logging enabled, and your support can use the log files to help your customer.

為您提供一個有用的示例:想想一個不能正常登入的軟體,但是當您的客戶打電話并因為遇到問題而請求幫助時,您發送給他的隻是一個更新的XML配置檔案,現在他已經已啟用日志記錄,您的支援人員可以使用日志檔案來幫助您的客戶。

And now, when you replace names a little bit, you end up with a simple implementation of a Service Locator , which is one of two patterns for Inversion of Control (since you invert control over who decides what exact class to instantiate).

現在,當您稍微更換名稱時,最終會得到一個服務定位器的簡單實作,這是控制反轉的兩種模式之一 (因為您可以控制誰決定要執行個體化的确切類)。

All in all this reduces dependencies in your code, but now all your code has a dependency to the central, single service locator.

總而言之,這減少了代碼中的依賴關系,但現在所有代碼都依賴于中央單一服務定位器。

Dependency injection is now the next step in this line: Just get rid of this single dependency to the service locator: Instead of various classes asking the service locator for an implementation for a specific interface, you - once again - revert control over who instantiates what.

依賴注入現在是這一行的下一步:隻需要去掉服務定位器的這個單一依賴:代替各種類詢問服務定位器是否有特定接口的實作,你再一次 - 恢複對誰執行個體化什麼的控制。

With dependency injection, your

Database

class now has a constructor that requires a parameter of type

ICanLog

:

通過依賴注入,您的

Database

類現在有一個構造函數,它需要一個

ICanLog

類型的參數:
public Database(ICanLog logger) { ... }
           

Now your database always has a logger to use, but it does not know any more where this logger comes from.

現在你的資料庫總是有一個記錄器可供使用,但它不知道這個記錄器的來源。

And this is where a DI framework comes into play: You configure your mappings once again, and then ask your DI framework to instantiate your application for you.

這就是DI架構發揮作用的地方:您再次配置映射,然後讓您的DI架構為您執行個體化您的應用程式。

As the

Application

class requires an

ICanPersistData

implementation, an instance of

Database

is injected - but for that it must first create an instance of the kind of logger which is configured for

ICanLog

.

由于

Application

類需要

ICanPersistData

實作,是以會注入一個

Database

執行個體 - 但為此必須先建立一個為

ICanLog

配置的logger執行個體。

And so on ...

等等 ...

So, to cut a long story short: Dependency injection is one of two ways of how to remove dependencies in your code.

是以,簡而言之:依賴注入是如何在代碼中删除依賴關系的兩種方法之一。

It is very useful for configuration changes after compile-time, and it is a great thing for unit testing (as it makes it very easy to inject stubs and / or mocks).

它對于編譯後的配置更改非常有用,對于單元測試來說它是一件好事(因為它可以很容易地注入存根和/或模拟)。

In practice, there are things you can not do without a service locator (eg, if you do not know in advance how many instances you do need of a specific interface: A DI framework always injects only one instance per parameter, but you can call a service locator inside a loop, of course), hence most often each DI framework also provides a service locator.

在實踐中,有些事情沒有服務定位器就無法做到(例如,如果你事先不知道你需要多少個特定接口的執行個體:一個DI架構總是每個參數隻注入一個執行個體,但你可以調用當然,循環内的服務定位器),是以大多數情況下每個DI架構也提供服務定位器。

But basically, that's it.

但基本上就是這樣。

Hope that helps.

希望有所幫助。

PS: What I described here is a technique called constructor injection , there is also property injection where not constructor parameters, but properties are being used for defining and resolving dependencies.

PS:我在這裡描述的是一種稱為構造函數注入的技術,還有屬性注入 ,其中沒有構造函數參數,但屬性用于定義和解析依賴項。

Think of property injection as an optional dependency, and of constructor injection as mandatory dependencies.

将屬性注入視為可選依賴項,将構造函數注入視為必需依賴項。

But discussion on this is beyond the scope of this question.

但對此的讨論超出了這個問題的範圍。

#3樓

I think a lot of times people get confused about the difference between dependency injection and a dependency injection framework (or a container as it is often called).

我想很多時候人們對依賴注入和依賴注入架構 (或者通常稱為容器 )之間的差別感到困惑。

Dependency injection is a very simple concept.

依賴注入是一個非常簡單的概念。

Instead of this code:

而不是這段代碼:
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();
}
           

you write code like this:

你寫這樣的代碼:
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();
}
           

And that's it.

就是這樣。

Seriously.

認真。

This gives you a ton of advantages.

這給你帶來了很多好處。

Two important ones are the ability to control functionality from a central place (the

Main()

function) instead of spreading it throughout your program, and the ability to more easily test each class in isolation (because you can pass mocks or other faked objects into its constructor instead of a real value).

兩個重要的是能夠從中心位置(

Main()

函數)控制功能,而不是在整個程式中傳播它,并且能夠更容易地隔離測試每個類(因為你可以将模拟或其他僞造的對象傳遞到它的構造函數而不是實際值)。

The drawback, of course, is that you now have one mega-function that knows about all the classes used by your program.

當然,缺點是你現在有一個知道你的程式使用的所有類的超級函數。

That's what DI frameworks can help with.

這就是DI架構可以提供的幫助。

But if you're having trouble understanding why this approach is valuable, I'd recommend starting with manual dependency injection first, so you can better appreciate what the various frameworks out there can do for you.

但如果您無法了解為什麼這種方法很有價值,我建議首先從手動依賴注入開始,這樣您就可以更好地了解那裡的各種架構可以為您做些什麼。

#4樓

The main reason to use DI is that you want to put the responsibility of the knowledge of the implementation where the knowledge is there.

使用DI的主要原因是您希望将實施知識的責任放在知識所在的位置。

The idea of DI is very much inline with encapsulation and design by interface.

DI的概念非常符合界面封裝和設計。

If the front end asks from the back end for some data, then is it unimportant for the front end how the back end resolves that question.

如果前端從後端詢問某些資料,那麼前端後端如何解決該問題并不重要。

That is up to the requesthandler.

這取決于requesthandler。

That is already common in OOP for a long time.

這在OOP中已經很常見了很長時間。

Many times creating code pieces like:

很多時候建立代碼片段如:
I_Dosomething x = new Impl_Dosomething();
           

The drawback is that the implementation class is still hardcoded, hence has the front end the knowledge which implementation is used.

缺點是實作類仍然是寫死的,是以前端具有使用實作的知識。

DI takes the design by interface one step further, that the only thing the front end needs to know is the knowledge of the interface.

DI通過接口進一步采用設計,前端唯一需要知道的是接口的知識。

In between the DYI and DI is the pattern of a service locator, because the front end has to provide a key (present in the registry of the service locator) to lets its request become resolved.

在DYI和DI之間是服務定位器的模式,因為前端必須提供密鑰(存在于服務定位器的系統資料庫中)以使其請求得到解決。

Service locator example:

服務定位器示例:
I_Dosomething x = ServiceLocator.returnDoing(String pKey);
           

DI example:

DI例子:
I_Dosomething x = DIContainer.returnThat();
           

One of the requirements of DI is that the container must be able to find out which class is the implementation of which interface.

DI的一個要求是容器必須能夠找出哪個類是哪個接口的實作。

Hence does a DI container require strongly typed design and only one implementation for each interface at the same time.

是以,DI容器需要強類型設計,并且每個接口同時隻需要一個實作。

If you need more implementations of an interface at the same time (like a calculator), you need the service locator or factory design pattern.

如果您需要同時實作更多接口(如電腦),則需要服務定位器或工廠設計模式。

D(b)I: Dependency Injection and Design by Interface.

D(b)I:依賴注入和接口設計。

This restriction is not a very big practical problem though.

這種限制雖然不是一個很大的實際問題。

The benefit of using D(b)I is that it serves communication between the client and the provider.

使用D(b)I的好處是它服務于用戶端和提供者之間的通信。

An interface is a perspective on an object or a set of behaviours.

界面是對象或一組行為的透視圖。

The latter is crucial here.

後者在這裡至關重要。

I prefer the administration of service contracts together with D(b)I in coding.

我更喜歡在編碼時與D(b)I一起管理服務合同。

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.

在我的觀點中,使用D(b)I作為技術解決方案而沒有組織管理服務合同并不是非常有益,因為DI隻是一個額外的封裝層。

But when you can use it together with organizational administration you can really make use of the organizing principle D(b)I offers.

但是當你可以将它與組織管理一起使用時,你可以真正地利用我提供的組織原則D(b)。

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.

當你在寫死類中有一個隐式接口時,那麼随着時間的推移,當你使用D(b)I将其顯式化時,它的可通信性要小得多。

It all boils down to maintenance, which is over time and not at a time.

這一切都歸結為維護,這是随着時間的推移,而不是一次。

:-)

:-)

#5樓

As the other answers stated, dependency injection is a way to create your dependencies outside of the class that uses it.

正如其他答案所述,依賴注入是一種在使用它的類之外建立依賴項的方法。

You inject them from the outside, and take control about their creation away from the inside of your class.

你從外面注入它們,并從你的班級内部控制他們的創造。

This is also why dependency injection is a realization of the Inversion of control (IoC) principle.

這也是依賴注入是控制反轉 (IoC)原理的實作的原因。

IoC is the principle, where DI is the pattern.

IoC是原則,其中DI是模式。

The reason that you might "need more than one logger" is never actually met, as far as my experience goes, but the actualy reason is, that you really need it, whenever you test something.

就我的經驗而言,你可能“需要多個記錄器”的原因從未真正得到滿足,但實際的原因是,無論何時你測試某些東西,你真的需要它。

An example:

一個例子:

My Feature:

我的特點:
When I look at an offer, I want to mark that I looked at it automatically, so that I don't forget to do so. 當我看到報價時,我想标記我自動檢視它,以便我不會忘記這樣做。

You might test this like this:

您可以像這樣測試:
[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));
}
           

So somewhere in the

OfferWeasel

, it builds you an offer Object like this:

是以在

OfferWeasel

某個地方,它會為你建構一個這樣的商品對象:
public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}
           

The problem here is, that this test will most likely always fail, since the date that is being set will differ from the date being asserted, even if you just put

DateTime.Now

in the test code it might be off by a couple of milliseconds and will therefore always fail.

這裡的問題是,這個測試很可能總是失敗,因為正在設定的日期将與被聲明的日期不同,即使你隻是将

DateTime.Now

放在測試代碼中它可能會被關閉幾毫秒是以總會失敗。

A better solution now would be to create an interface for this, that allows you to control what time will be set:

現在更好的解決方案是為此建立一個接口,允許您控制将設定的時間:
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;
    }
}
           

The Interface is the abstraction.

接口是抽象。

One is the REAL thing, and the other one allows you to fake some time where it is needed.

一個是真實的東西,另一個允許你假裝需要它的時間。

The test can then be changed like this:

然後可以像這樣更改測試:
[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.

例如,這裡的接口和類是不必要的,因為在C#函數中可以作為變量傳遞,是以您可以使用

Func<DateTime>

來實作相同的接口而不是接口。

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.

或者,如果采用動态方法,則隻傳遞具有等效方法的任何對象( 鴨子類型 ),并且根本不需要接口。

You will hardly ever need more than one logger.

您幾乎不需要多個記錄器。

Nonetheless, dependency injection is essential for statically typed code such as Java or C#.

盡管如此,依賴注入對于靜态類型代碼(如Java或C#)至關重要。

And... It should also be noted that an object can only properly fulfill its purpose at runtime, if all its dependencies are available, so there is not much use in setting up property injection.

并且......還應該注意,如果一個對象的所有依賴項都可用,那麼它隻能在運作時正确地實作其目的,是以在設定屬性注入時沒有多大用處。

In my opinion, all dependencies should be satisfied when the constructor is being called, so constructor-injection is the thing to go with.

在我看來,在調用構造函數時應該滿足所有依賴項,是以構造函數注入是最佳選擇。

I hope that helped.

我希望有所幫助。

#6樓

I think the classic answer is to create a more decoupled application, which has no knowledge of which implementation will be used during runtime.

我認為經典的答案是建立一個更加分離的應用程式,它不知道在運作時将使用哪個實作。

For example, we're a central payment provider, working with many payment providers around the world.

例如,我們是一家中央支付提供商,與世界各地的許多支付提供商合作。

However, when a request is made, I have no idea which payment processor I'm going to call.

但是,當提出請求時,我不知道我要打電話給哪個支付處理器。

I could program one class with a ton of switch cases, such as:

我可以使用大量的開關案例編寫一個類,例如:
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.

現在想象一下,現在你需要在一個類中維護所有這些代碼,因為它沒有正确解耦,你可以想象,對于你支援的每個新處理器,你需要建立一個新的if // switch案例每個方法,這隻會變得更複雜,但是,通過使用依賴注入(或控制反轉 - 因為它有時被稱為,意味着無論誰控制程式的運作隻在運作時知道,而不是複雜),你可以實作一些東西非常整潔和可維護。
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();
}
           

** The code won't compile, I know :)

**代碼不會編譯,我知道:)