最近在看公司前輩的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();
}
}
在這個簡單的打電動例子中,Human
的gamingConsole
這個變數被我們在建構子中寫死成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的方法大致上分為兩種:
- Constructor Injection (上述方法一)
- Setter Injection (上述方法二)
目前聽起來好像Dependency Injection好棒棒,但其實有一些缺點,而Guice就是Google提出來解決Dependency Injection的這些問題。
結論
Class之間Dependency太強主要會有兩個大問題:
- 程式碼無法重複使用。
- 很難做Unit Test。
Dependency Injection (DI)的技巧 (在Java中通常會搭配interface來使用)就是為了解決兩個class之間dependency太強烈的問題。主要有兩種做法:
- Constructor Injection
- Setter Injection
Dependency Injection也有一些缺點,而Guice就是Google為了解決這些問題而提出來的一種解法。