依賴(lài)注入

概述

依賴(lài)注入(Denpendecy Injection ,DI) 通常和 控制反轉(zhuǎn)(Inverse of Control,IoC) 一起出現(xiàn).它是實(shí)現(xiàn)IoC的主要手段之一.通過(guò)依賴(lài)注入類(lèi)可以不關(guān)心自身的依賴(lài)應(yīng)該如何構(gòu)造,而是由注入器代理這個(gè)職責(zé),將類(lèi)需要的依賴(lài)構(gòu)建好后注入到類(lèi)里.可以達(dá)到分離關(guān)注點(diǎn),分離調(diào)用方和依賴(lài),提高可復(fù)用性和可維護(hù)性.

為什么需要依賴(lài)注入

為什么需要依賴(lài)注入呢? 這和為什么需要IoC的原因基本相同.

在常規(guī)的開(kāi)發(fā)過(guò)程中,很多時(shí)候一個(gè)類(lèi)都是要依賴(lài)于其他的類(lèi)才能實(shí)現(xiàn)某些功能,才能夠更好的將關(guān)注點(diǎn)分離,比如一個(gè)車(chē) Car 類(lèi) 內(nèi)部需要使用燃油引擎 Engine 類(lèi),那么他需要在 Car 的內(nèi)部做類(lèi)似 new Engine() 這樣的操作, 看起來(lái)沒(méi)有任何的問(wèn)題,但是當(dāng)需要更換引擎為他的子類(lèi)電氣引擎ElectricEngine 的時(shí)候,問(wèn)題就來(lái)了,我們需要在 Car 的內(nèi)部修改 Engine 類(lèi),或者是當(dāng) Engine 做了修改需要額外的參數(shù)的時(shí)候,也會(huì)需要改動(dòng)到 Car 的內(nèi)部,并且每個(gè)類(lèi)都得對(duì)自身的依賴(lài)有更多的了解,這就違反了底米特原則(todo) 和 開(kāi)閉原則(todo)導(dǎo)致了代碼的耦合度極高,不利于維護(hù)和擴(kuò)展.

如圖

image

這個(gè)時(shí)候我們將這個(gè)依賴(lài)創(chuàng)建獲得的權(quán)利交給外部去維護(hù),那么這個(gè)類(lèi)只需要聲明他需要一個(gè) Engine 即可,至于是燃油還是電氣的引擎就由外部控制,此時(shí)依賴(lài)不再是內(nèi)部生成,而是通過(guò)外部注入得到,再碰到這樣的問(wèn)題的時(shí)候,就不需要更改 Car 內(nèi)部的代碼了.而是由外部代碼控制即可.

如圖

image

依賴(lài)注入尾為我們解決了以下問(wèn)題:

  • 如何讓一個(gè)類(lèi)和如何創(chuàng)建這個(gè)類(lèi)互不關(guān)聯(lián)
  • 如何讓類(lèi)支持一定程度的可變性,提高可復(fù)用度
  • 如何通過(guò)一些外部配置來(lái)控制對(duì)象的創(chuàng)建
  • 如何讓一個(gè)應(yīng)用支持不同的配置

細(xì)節(jié)

組成結(jié)構(gòu)

依賴(lài)注入將依賴(lài)的創(chuàng)建和依賴(lài)的行為隔離開(kāi),這使得程序變得更加松耦合并且更加符合依賴(lài)反轉(zhuǎn)原則(todo)和單一職責(zé)原則(todo).

最完全的依賴(lài)注入包含以下幾個(gè)部分

  • 服務(wù). 要使用的對(duì)象.
  • 客戶端. 依賴(lài)服務(wù)的對(duì)象.
  • 接口. 定義客戶端如何使用服務(wù).
  • 注入器. 構(gòu)建服務(wù)并注入到客戶端中.

UML圖如下:

在這里插入圖片描述

用前面的汽車(chē)的例子對(duì)應(yīng)

  • 服務(wù). Engine, ElectricEngine
  • 客戶端. Car
  • 接口. 比如 Engine 實(shí)現(xiàn)了一個(gè) IEngine 接口, Car 內(nèi)部使用 IEngine 前面的例子簡(jiǎn)化了這一步,我們可以看到像Spring 中使用的時(shí)候有時(shí)候也是會(huì)存在簡(jiǎn)化的情況.
  • 注入器. 將Engine 注入到 Car 中的對(duì)象.

常見(jiàn)的簡(jiǎn)化是直接使用了父子類(lèi)來(lái)替代接口進(jìn)行操作,這個(gè)需要根據(jù)情況具體分析.當(dāng)然,這么做勢(shì)必會(huì)違反依賴(lài)倒置原則和影響解耦程度.所以最優(yōu)的情況是按照要求完整的實(shí)現(xiàn)它.

客戶端不應(yīng)該知道具體的依賴(lài)實(shí)現(xiàn),而是只關(guān)心接口,而接口后面的變化則不會(huì)對(duì)客戶端產(chǎn)生任何影響.

注入器通常也有很多別的名稱(chēng),比如 提供者,容器,工廠等

實(shí)現(xiàn)方式

依賴(lài)注入有如下實(shí)現(xiàn)方式:

  • 手動(dòng)注入. 不利于擴(kuò)展,當(dāng)程序不斷龐大的時(shí)候,維護(hù)難度變高,并且需要維護(hù)大量的模板代碼.
    • 基于接口.實(shí)現(xiàn)特定接口以供外部容器注入所依賴(lài)類(lèi)型的對(duì)象.優(yōu)點(diǎn)是依賴(lài)可以完全不了解客戶端,都對(duì)接口負(fù)責(zé)即可.
    • 基于 set 方法.實(shí)現(xiàn)特定屬性的 set 方法,來(lái)讓外部容器注入所依賴(lài)類(lèi)型的對(duì)象.靈活性較高,任何時(shí)候都可以修改,但是由于過(guò)于靈活,無(wú)法確保依賴(lài)的完整,而且在所需依賴(lài)較多的時(shí)候,調(diào)用者難以判斷需要哪些依賴(lài).是否注入完整.并且暴露了注入接口,可能被其他對(duì)象意外調(diào)用.
    • 基于構(gòu)造函數(shù).實(shí)現(xiàn)特定參數(shù)的構(gòu)造函數(shù),在新建對(duì)象時(shí)注入所依賴(lài)類(lèi)型的對(duì)象. 使得依賴(lài)在剛創(chuàng)建對(duì)象的時(shí)候就被注入,保證了可靠性.但是靈活性較低,后面無(wú)法修改.
  • 自動(dòng)注入. 便于維護(hù),開(kāi)發(fā)效率較高,代碼清晰
    • 基于注解. 基于Java的注解功能(todo),在私有變量前加 @Autowired 等注解,不需要顯式的定義手動(dòng)注入的那些代碼,就可以讓外部容器注入對(duì)應(yīng)的對(duì)象.相當(dāng)于定義了public的set方法,但是因?yàn)闆](méi)有真正的set方法,減少了暴露的接口(使用set方法的話必須設(shè)置set方法為 public , 導(dǎo)致外部其他對(duì)象都可以顯式隨意的訪問(wèn)這個(gè)接口,但是這個(gè)依賴(lài)是否想要讓外部訪問(wèn)修改還是個(gè)未知數(shù).)

一般情況下使用 自動(dòng)注入,注解注入的情況比較多.

目前比如 Spring,Guice,Dagger 等框架都實(shí)現(xiàn)了依賴(lài)注入的功能.

代碼示例

  • 沒(méi)有依賴(lài)注入的情況
/** Client without DI */
  public class Client {
    // Internal reference to the service used by this client
    private ServiceImpl service;

    Client() {
      // Specify a specific implementation in the constructor instead of using dependency injection
      service = new ServiceImpl();
    }

    public void doSth() {
      service.doSth();
    }
  }
  • 基于接口
/** Client with interface DI */
  public class Client implements ServiceSetter {
    // Internal reference to the service used by this client
    private Service service;

    Client() {}

    public void doSth() {
      service.doSth();
    }

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

  /** Service setter interface. */
  public interface ServiceSetter {
    void setService(Service service);
  }

?

  • 基于 set 方法
/** Client with setter DI */
public class Client {
  // Internal reference to the service used by this client
  private Service service;

  Client() {}

  public void doSth() {
    service.doSth();
  }

    /**
    * set dependency in .
    */
  public void setService(Service service) {
    this.service = service;
  }
}
  • 基于構(gòu)造器
/** Client with constructor DI */
public class Client {
  // Internal reference to the service used by this client
  private Service service;

  // set dependency with constructor
  Client(Service service) {
    this.service = service;
  }

  public void doSth() {
    service.doSth();
  }
}
  • 基于注解
/** Client with annotation DI */
public class Client {
  // Internal reference to the service used by this client
  @Inject
  private Service service;

  Client() {}

  public void doSth() {
    service.doSth();
  }
}

優(yōu)點(diǎn)

  • 提高了客戶端的靈活性,使得代碼的可配置性得到提高. 客戶端只關(guān)注自己需要的接口行為,而不強(qiáng)行綁定接口背后的實(shí)現(xiàn)邏輯,使得實(shí)現(xiàn)邏輯在一定程度上可以改變而不影響到客戶端.
  • 使得應(yīng)用可以同時(shí)適配多種情況,通過(guò)注入不同的對(duì)象實(shí)現(xiàn)不同的邏輯.程序靈活性大大提高,在測(cè)試等方面有顯著的效果.
  • 分離關(guān)注點(diǎn).客戶端不再關(guān)心依賴(lài)的創(chuàng)建和許多細(xì)節(jié)問(wèn)題.這樣更有利于單元測(cè)試等.
  • 提高了代碼的復(fù)用性,可測(cè)試性和可維護(hù)性.
  • 減少了模板代碼,將創(chuàng)建依賴(lài)都交給了注入器去管理.
  • 更有利于協(xié)同開(kāi)發(fā).由于大家都只對(duì)接口負(fù)責(zé),那么不同的模塊也可以方便有效的交給不同的人開(kāi)發(fā).類(lèi)似插件的開(kāi)發(fā)方式
  • 減少了客戶端和依賴(lài)間的耦合
  • 分離了類(lèi)的使用和創(chuàng)建

缺點(diǎn)

  • 如果沒(méi)有使用合適的方法,會(huì)導(dǎo)致一些存在比較固定的默認(rèn)對(duì)象的創(chuàng)建過(guò)程重復(fù)太多次,不利于維護(hù).

  • 提高了代碼的學(xué)習(xí)成本,開(kāi)發(fā)者必須了解DI的原理并且額外找到注入的對(duì)象才能理解這部分的邏輯.

  • 提高了代碼的使用難度.由于DI通常是通過(guò)反射或者APT技術(shù)實(shí)現(xiàn)的,所以對(duì)于IDE中的一些自動(dòng)化操作會(huì)有所影響,比如 找到引用類(lèi),調(diào)用層級(jí)之類(lèi)的.

  • 提高了前期開(kāi)發(fā)的成本.由于引入了DI技術(shù),導(dǎo)致前期初始化依賴(lài)要做的事情變得復(fù)雜.雖然后期的維護(hù)成本將大大下降.

About Me

我的博客 leonchen1024.com

我的 GitHub https://github.com/LeonChen1024

微信公眾號(hào)

在這里插入圖片描述
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。