如何用最簡單的方式解釋依賴注入?依賴注入是如何實現解耦的?


如何用最簡單的方式解釋:

____________________________________________________________

還是一步一步解釋最為簡單,好比搬用數學公式,不理解還是不懂。

____________________________________________________________

假如有一個 船(C)類 ,一個 槳(J) 類,

class C{
J j = new J() ;
}

如果船要幹什麼事,肯定需要漿的參與。所以是十分 「依賴」漿;

出了需求需要重構:這時候我們需要控制漿的長度為10在構造方法中。我們需要這麼寫;

class C{
J j = new J(10) ;
}

一個特性需要修改漿構造方法,又需要修改船其中的new J()方法。這時候就設計者就思考,為什麼我們加入一個特性需要更改兩個類中代碼(這也就是耦合度高)!

所以我們要解耦要依賴注入;

常用解耦方式:

  • 構造方法注入

如下:我重構代碼的時候在也不用看哪裡的漿還是短的了!因為船構造方法依賴了漿。任你漿怎麼設計,我用的時候傳一個漿進來即可。(下層依賴上層,用的時候傳入,而不是針對下層去修改)

class C{
J j
public c(J j){
this.j = j;
};
}

  • 工廠模式注入

工廠模式 Human 人 去注入; 工廠類如下

Class Human {
J j =new J();
J getJ(){
return j ;
}
}

此時如下:不管你怎麼改漿,改成100米與船都無關,他只要依賴Human,

一千個船修改漿需求我只修改Human類中方法便可。(核心業務邏輯需要依賴的類實例化交給第三方類來實現注入。)

Class C {
J j ;
Human h = new Human;
j=Human.getJ();
}

  • 框架注入(本質還是工廠設計模式的具體實現)

本質也是第三方依賴注入,但是這個第三方可以脫離類。將對象依賴映射信息存儲在容器一般為.xml 或者特定的對象中,並實現動態的注入。你需要我就給你哦。

最後本人個人理解:

為什麼要有依賴注入(一種設計代碼模式),因為我們要控制反轉(設計代碼的思路)。為什麼控制反轉。因為我們軟體設計需要符合軟體設計原則依賴倒置(設計代碼原則),單一職責原則。

說通俗點,咱們要解耦啊。

MVP模式就是解耦比較全面的設計模式模型,


只講原理,不講過程。

大多數面向對象編程語言,在調用一個類的時候,先要實例化這個類,生成一個對象。

如果你在寫一個類,過程中要調用到很多其它類,甚至這裡的其它類,也要「依賴」於更多其它的類,那麼可以想像,你要進行多少次實例化。

這就是「依賴」的意思。

依賴注入,全稱是「依賴注入到容器」, 容器(IOC容器)是一個設計模式,它也是個對象,你把某個類(不管有多少依賴關係)放入這個容器中,可以「解析」出這個類的實例。

所以依賴注入就是把有依賴關係的類放入容器(IOC容器)中,然後解析出這個類的實例。僅此而已。

如果你看的是PHP,可以看看我的博客:

Laravel 5.1 文檔攻略 ——Service Container


看完了答案,其實有一點很重要的沒有說到,就是你自己注入的東西,你想怎麼樣改就怎麼樣改,好比我面試會問ioc和aop什麼關係?

ioc最主要是解耦和方便附加功能(用aop實現)。

思想上

上帝說,要有光,於是有了光。誰給了上帝光,容器啊,你只需要知道你要什麼(很多時候是介面),你不需要知道怎麼來的的。

簡單理解你就是一個富二代,你要什麼不需要自己操心,開口就行,你的富爸爸(ioc容器)都給你包辦了!爽不爽?

再具體一點,你對老爸說,我要一輛車,車就是介面Car,你老爸給了你一輛B字頭的車,可能是比亞迪,也可能賓士,比亞迪和背馳就是介面Car的具體實現,而你不需要知道具體是什麼車,只需要知道是車就可以了。


依賴注入(DI)和控制反轉(IOC)基本是一個意思,因為說起來誰都離不開誰。

簡單來說,a依賴b,但a不控制b的創建和銷毀,僅使用b,那麼b的控制權交給a之外處理,這叫控制反轉(IOC),而a要依賴b,必然要使用b的instance,那麼

  1. 通過a的介面,把b傳入;
  2. 通過a的構造,把b傳入;
  3. 通過設置a的屬性,把b傳入;

這個過程叫依賴注入(DI)。

那麼什麼是IOC Container?

隨著DI的頻繁使用,要實現IOC,會有很多重複代碼,甚至隨著技術的發展,有更多新的實現方法和方案,那麼有人就把這些實現IOC的代碼打包成組件或框架,來避免人們重複造輪子。

所以實現IOC的組件或者框架,我們可以叫它IOC Container。


覺得前幾位講得都挺好, 直接看代碼吧直觀點...Java程序

緊耦合有依賴的寫法:

class Employee {
Address address;
Employee() {
address = new Address();
}
}

松耦合沒依賴的寫法:

class Employee {
Address address;
Employee(Address address) {
this.address=address;
}
}

依賴注入最大的兩個好處是:

  • 代碼松耦合, 易維護
  • 易測試

我這麼理解,針對的是Symfony的依賴注入:

依賴注入是不在類中實例化其他依賴的類,而是先把依賴的類實例化了,然後以參數的方式傳入構造函數中;

當類的數量變多,依賴關係變得複雜後,需要引入一個容器來管理各個類的實例化工作。

而以介面而不是類名來定義類的依賴關係,這樣其他類只需要實現必須的介面,就可以方便的在容器中通過修改配置進行替換,實現解耦。


看了所有答案,發現大多數人的答案模稜兩可,甚至還有誤導性,特來回答一下,首先得先說清幾個名詞 IoC, DI, SL, DIP。

IoC(控制反轉)目前比較流行的兩種方式 DI(依賴注入模式) 和 SL(伺服器定位模式),DI 是遵循 DIP(依賴反轉原則)的,SL 是 anti-pattern(反模式)的,不遵循 DIP。

因為注入的形式五花八門,為了代碼復用性和擴展性出現了 IoC container 這麼個東西,它是遵循各自 DI/SL 模式實現的容器,DI 容器會大量的運用反射機制來實現。@唐思 提到的 Laravel 中 Service Container 核心是 illuminate/container 是 DI/SL 的混合體。

其他就不多說了,關於這個概念,可以多讀幾遍下面這篇文章:

Inversion of Control Containers and the Dependency Injection pattern [譯文]


原文: 故事:貓的出生理解依賴注入 控制反轉

故事:貓的出生理解依賴注入 控制反轉

大場景: 貓必須吃可以讓他們變色的水果 才能成為真正不同顏色的貓! 比如橘貓要吃橘子!

場景: 貓的出生

強耦合關係:

貓A和上帝的故事:

上帝:你想成為高貴的橘貓,就必須自己生產橘子,我把秘方給你!你生產好了 自己吃掉 就能出生並且變成真正的橘貓了!

貓A:好!我這就生產。

貓A:死活生產不出來橘子,原來秘方是錯的!

上帝:嘿嘿嘿~ 那就別出生了唄。你自己生產不出來。

貓A:媽賣批!

class 橘貓A{

public $貓吃的橘子;

public function __contruct(){

$this-&>貓吃的橘子 = new 橘子秘方;

}

}

橘子秘方{

}

依賴注入 解除耦合:

貓B和上帝的故事:

上帝:你想成為高貴的橘貓,就必須自己生產橘子,我把秘方給你!你生產好了 自己吃掉 就能出生並且變成真正的橘貓了!

貓B:我不要自己生產,貓A都 「媽賣批」了!你就不能生產好了再給我吃嗎?

上帝尷尬:那好吧,答應你!

橘貓A(客串):媽賣批!

class 貓B{

public $貓吃的橘子;

public function __contruct(橘子){

$this-&>貓吃的橘子 = 橘子;

}

}

橘子秘方{

}

$橘貓B = new 貓B(new 橘子秘方);

控制反轉

貓C和上帝的故事

上帝說:你想成為高貴的橘貓,就必須自己生產橘子,我把秘方給你!你生產好了 自己吃掉 就能出生並且變成真正的橘貓了!

貓C:別BB,你都給貓B上產了,還讓我自己生產,有毛病吧?

上帝尷尬:呵呵- -~~~ 那你想怎樣?

貓C:都特么想當橘貓~呵呵 我要當 」變色貓! 「

上帝:我日!有志氣!

貓C:少BB 快來吧。

class 貓C{

public $貓吃的變色水果;

public function __contruct(變色水果){

$this-&>貓吃的變色水果 = 變色水果;

}

}

變色水果秘方(介面){

public function bianseinit(){}

}

上帝:上面就是給你的出生路徑了,那你到底是想變成什麼貓呢 橘貓 粉貓?

貓C:先來個 橘貓吧!

上帝:媽賣批!!!

橘子秘方 implements 變色水果秘方{

}

貓C:謝謝上帝 我還要 變成粉貓的果實!!

上帝:媽賣批

水蜜桃秘方 implements 變色水果秘方{

}

..

.. 貪婪無厭~!

$變色貓C = new 貓c(new 橘子秘方水蜜桃秘方.......);

貓A(客串):媽賣批!

貓B(客串):媽賣批

故事結局END

上帝被痛扁一頓。


簡單的回答吧,因為大家都喜歡依賴介面,那麼就會出現類似於:

var a=new A();
a.Ib=new B();
a.Ic=new C();
....

這樣很噁心的代碼,所以就有了依賴注入。


面向介面而不是面向具體的類,依賴抽象而不是依賴具體


spring的ioc本質上用了工廠模式的思想 你在某個業務邏輯的類中需要引用一個持久化層對象,你不需要自己創建它,而是向ioc容器(工廠)拿


多個服務登記在一個服務,需要什麼從這裡面取…


額,感覺我們把dependency injection(依賴注入)想的太複雜了。就像一碗粉絲賣出了魚翅的價格一樣。

首先,依賴注入就是:向我們的程序中傳入了一個已經被實例化好了變數,這個變數內部的改變我們不需要知道,只要這個變數的介面沒有變。先記住這句話,我們再來舉一個例子。

來,把我們想像成一個程序應用。

今天我們(程序)要從武漢去深圳。需要幾步?可以這樣子:

  1. 我們(程序)在去哪網買機票
  2. 我們(程序)從滴滴叫車
  3. 我們(程序)坐車從家到機場
  4. 我們(程序)坐飛機從武漢到深圳。

如果,去哪網出問題了,我們(程序)需要換成攜程網買機票;

如果,滴滴出問題了,我們(程序)需要在街上攔的士;

怎麼去簡化這個過程呢?

如果,買機票和叫車這兩件事情我們讓秘書來訂,不管機票是在去哪網還是攜程網買的,的士是手機app訂的,還是路上攔的。我們(程序)只用做三件事情,讓秘書訂票,叫車,坐車去機場,從飛機從武漢到深圳。

所以,秘書在這個場景中是什麼?就是一個已經可以使用的變數。至於這個秘書(變數)怎麼去工作的,我們不需要知道。

先寫這麼多,第二個問題之後再來和大家討論。


補充一下4樓的答案:

給定一個場景,類A需要使用類B提供的某些方法,所以A依賴於B。

控制反轉(IOC)關注的是對象該由「誰」來管理(包括創建和銷毀),這個「誰」就是IOC容器。當容器負責管理對象的創建和銷毀時,對象的控制權就不在使用這個對象的使用者手中,在上面的場景里類A需要使用類B,但是A不直接創建B,也不關心B如何被容器創建,A只管向容器要B的實例。

那麼問題來了,容器創建好了B的實例後怎麼交到A的手裡呢?通過依賴注入(DI)

介面注入,setter注入,構造器注入這三種方式任選其一,給容器打開一扇門,容器就會穿過這扇門把B的實例交到A手中。

所以依賴注入關注的是對象實例的交付方式。IOC容器創建好了實例,DI將實例交到使用者手中。


我舉個簡單的例子

1.在原始社會人們想吃飯的話,需要自己找食材,然後自己用工具自己做。

2.後來社會上出現了一個叫飯店的地方,人們想吃飯只需要跑到飯店,然後給錢,飯就做好了。

3.再後來外賣出現了,你不需要去飯店,打開手機點一下,然後飯就來了。

然後分析下這三種情況

1.你需要知道如何做飯(構造方法)、找到食材(持有依賴),這個很明顯耦合比較嚴重,因為如果食材換了,你做飯的方法也要換。

2.你不需要知道如何做飯,也不需要找食材,你只需要找到飯店(工廠模式),這個已經很明顯降低了耦合,但是你需要知道飯店的位置(持有工廠的依賴)

3.你什麼都不需要知道,打開手機點一下就行了。這個解耦就很明顯了吧!

代碼我就不寫了

實現依賴注入的方式很多 比如 構造方法注入 Set方法注入 介面方式注入


對象A 持有對象 B , 構造對象A的時候, 需要構造對象B.

構造A時 對B構造, B能夠根據參數, 形成B對象 .


我們來看看Google Guice的依賴注入是怎麼用的

import javax.inject.Singleton;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

@Singleton
class HelloPrinter {

public void print() {
System.out.println("Hello, World");
}

}

@Singleton
public class Sample {

@Inject
private HelloPrinter printer;

public void hello() {
printer.print();
}

public static void main(String[] args) {
Injector injector = Guice.createInjector();
Sample sample = injector.getInstance(Sample.class);
sample.hello();
}

}

至於依賴注入是怎麼解耦的,在我看來代碼看起來清爽舒服,那就是解耦了。耦合大的模塊往往看起來又臭又長。

Guice使用入門


放上一段代碼:

namespace appmodels;
use yiiaseObject;
use yiidbConnection;
use yiidiContainer;

interface UserFinderInterface
{
function findUser();
}

class UserFinder extends Object implements UserFinderInterface
{
public $db;

public function __construct(Connection $db, $config = [])
{
$this-&>db = $db;
parent::__construct($config);
}

public function findUser()
{
}
}

class UserLister extends Object
{
public $finder;

public function __construct(UserFinderInterface $finder, $config = [])
{
$this-&>finder = $finder;
parent::__construct($config);
}
}

$container = new Container;
$container-&>set("yiidbConnection", [
"dsn" =&> "...",
]);
$container-&>set("appmodelsUserFinderInterface", [
"class" =&> "appmodelsUserFinder",
]);
$container-&>set("userLister", "appmodelsUserLister");

$lister = $container-&>get("userLister");

// which is equivalent to:

$db = new yiidbConnection(["dsn" =&> "..."]);
$finder = new UserFinder($db);
$lister = new UserLister($finder);

使用了依賴注入之後,代碼中調用

$lister = $container-&>get("userLister");

就等同於

$db = new yiidbConnection(["dsn" =&> "..."]);
$finder = new UserFinder($db);
$lister = new UserLister($finder);

是不是很方便?


我有個問題想請教一下大牛們, 置頂的回答是"依賴注入到容器",這個容器就是IOC.

問題來了,這個IOC是"控制反轉"嗎? 如果是的話,控制反轉不是與依賴注入和依賴查找沒什麼關係嗎?


樓上的只是思想局限於框架了,歸結根本只是反射而已。


推薦閱讀:

TAG:Java | Spring | JavaWeb | IOC | SpringMVC框架 |