Java學習筆記:想了解Google Guice Framework,先來認識Dependency Injection

隱市碼農
7 min readJun 3, 2021

--

最近在看公司前輩的java code,裡面用到了很多Google Guice Injection Framework相關的技術,所以就順勢好好研究了一下,並且把心得記錄下來,方便自己日後如果許久沒碰Java,需要複習時可以加快上手速度。

筆者在大學、研究所求學過程中,得到的一個受用無窮的觀念就是:

不要急著深入探討一項新技術的細節,而是要先了解為什麼會需要這個技術?它的價值在哪?它解決什麼樣的問題?

所以筆者在開始理解Guice的語法之前,先問自己的第一個問題:Guice解決了什麼問題?

要講到Guice解決的問題,就得先提到Dependency Injection (DI)的概念。用一個打電動的範例程式碼當例子來解釋:

public class Human {
private GamingConsole gamingConsole;
public Human() {
this.gamingConsole = new NitendoSwitch();
}
public void relax() {
gamingConsole.play();
}
}
public interface GamingConsole {
public void play();
}
public class NitendoSwitch implements GamingConsole {
@override
public void play() {
System.out.println("Play with Nitendo Switch.");
}
}
public class TestHuman {
public static void main(String[] args) {
Human me = new Human();
me.relax();
}
}

在這個簡單的打電動例子中,HumangamingConsole這個變數被我們在建構子中寫死成new NitendoSwitch(),這樣的結果就是我們在Human class以及NitendoSwitch class之間建立了一個dependency。

寫程式的時候,如果能夠盡量減少程式碼之間的dependency,程式碼才能增加被重複使用的可能性,程式的品質也越好。

以這個例子來說,當我們在Human以及NitendoSwitch之間建立了dependency,未來當筆者在這個專案的其他地方想要重複使用Human class,但這次希望改玩PS5,就會發現只能把Human class重新寫一遍 (e.g., 新的class稱為HumanWithPS5),因為這個Human被寫死只能玩Nitendo Switch。

而且這個dependency不只造成程式碼無法重複使用,還會造成另一個大問題 — 很難做測試。如果我們要對Human class做Unit Test,會發現測試的時候會連同GamingConsole interface以及NitendoSwitch class的程式碼都一起測試到 (因為我們在Human寫死要new NitendoSwitch()),而沒辦法把這三者拆開來單純一次測試一個部分。

現在筆者發現Human class以及NitendoSwitch class之間的dependency太強烈了,想要來修改,該怎麼修改了?以下提供兩種解法給大家當參考。

方法一:修改Human的建構子,要求傳入一個GamingConsole型別的變數,並修改main函式。

public class Human {
private GamingConsole gamingConsole;
public Human(GamingConsole gc) {
this.gamingConsole = gc;
}
public void relax() {
gamingConsole.play();
}
}
public interface GamingConsole {
public void play();
}
public class NitendoSwitch implements GamingConsole {
@override
public void play() {
System.out.println("Play with Nitendo Switch.");
}
}
public class TestHuman {
public static void main(String[] args) {
GamingConsole ns = new NitendoSwitch();
Human me = new Human(ns);
me.relax();
}
}

方法二:修改Human的建構子,讓其不要去設定gamingConsole的值,接著提供一個setter function用來設定gamingConsole,最後修改main函式。

public class Human {
private GamingConsole gamingConsole;
public Human() {}
public void relax() {
gamingConsole.play();
}
public void setGamingConsole(GamingConsole gc) {
this.gamingConsole = gc;
}
}
public interface GamingConsole {
public void play();
}
public class NitendoSwitch implements GamingConsole {
@override
public void play() {
System.out.println("Play with Nitendo Switch.");
}
}
public class TestHuman {
public static void main(String[] args) {
GamingConsole ns = new NitendoSwitch();
Human me = new Human();
me.setGamingConsole(ns);
me.relax();
}
}

這兩種方法使用的概念就是Dependency Injection。

所謂Dependency Injection,簡單來說就是指把一個物件A所依賴的物件B傳給物件A,並且我們稱class A的dependency為class B。

以這個例子來說,我們的Human class所實體化出來的物件me,需要用到GamingConsole型別的物件ns,因此可以說"Human class的dependency是GamingConsole interface”。我們在這兩個方法的程式碼中,成功的把ns物件"注入 (Inject)"給me物件。

Dependency Injection的方法大致上分為兩種:

  1. Constructor Injection (上述方法一)
  2. Setter Injection (上述方法二)

目前聽起來好像Dependency Injection好棒棒,但其實有一些缺點,而Guice就是Google提出來解決Dependency Injection的這些問題。

結論

Class之間Dependency太強主要會有兩個大問題:

  1. 程式碼無法重複使用。
  2. 很難做Unit Test。

Dependency Injection (DI)的技巧 (在Java中通常會搭配interface來使用)就是為了解決兩個class之間dependency太強烈的問題。主要有兩種做法:

  1. Constructor Injection
  2. Setter Injection

Dependency Injection也有一些缺點,而Guice就是Google為了解決這些問題而提出來的一種解法。

--

--

No responses yet