如何用最簡單的方式解釋依賴注入?依賴注入是如何實現解耦的?
如何用最簡單的方式解釋:
____________________________________________________________
還是一步一步解釋最為簡單,好比搬用數學公式,不理解還是不懂。
____________________________________________________________
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;
};
}
- 工廠模式注入
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,那麼
- 通過a的介面,把b傳入;
- 通過a的構造,把b傳入;
- 通過設置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(依賴注入)想的太複雜了。就像一碗粉絲賣出了魚翅的價格一樣。
首先,依賴注入就是:向我們的程序中傳入了一個已經被實例化好了的變數,這個變數內部的改變我們不需要知道,只要這個變數的介面沒有變。先記住這句話,我們再來舉一個例子。
來,把我們想像成一個程序應用。
今天我們(程序)要從武漢去深圳。需要幾步?可以這樣子:
- 我們(程序)在去哪網買機票
- 我們(程序)從滴滴叫車
- 我們(程序)坐車從家到機場
- 我們(程序)坐飛機從武漢到深圳。
如果,去哪網出問題了,我們(程序)需要換成攜程網買機票;
如果,滴滴出問題了,我們(程序)需要在街上攔的士;
怎麼去簡化這個過程呢?
如果,買機票和叫車這兩件事情我們讓秘書來訂,不管機票是在去哪網還是攜程網買的,的士是手機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框架 |