---
# System prepended metadata

title: Spring 探索筆記 ~ Inversion of Control (2)
tags: [' IoC', ' Inversion of Control', SpringTracing, ' SourceTracing', ' SpringBoot', ' Spring']

---

---
tags: SpringTracing, Spring, SpringBoot, SourceTracing, IoC, Inversion of Control
---

# Spring 探索筆記 ~ Inversion of Control (2)
## 在開始研究之前
在開始研究Spring 之前有些前置的基礎需要優先建立，這會讓我們在探索Spring 時，較不會迷失方向。
* **IoC**
* Factory Method Pattern
* ApplicationContext

## IoC (Inversion of Control)
在Java的領域裡；IoC 是可以算得上是相當重要的觀念，尤其對於近幾年使用Spring-Boot開發Web Application的開發者來說，IoC的運用可以說是隨處可見。

但我相信大多數的新手開發者應該都跟我一樣，雖然會根據官方文件使用各種Spring-Boot的註解；但都僅把IoC當作Spring一個公式化的用法而已，不會去思考其含義是什麼。

因此在走進Spring Framework原始碼的世界前，我花了些時間理解了IoC的觀念，也希望藉此系統性地將Spring學好。

## 定義 IoC
根據Wiki的定義，"*控制反轉（英語：Inversion of Control，縮寫為IoC），是物件導向程式設計中的一種設計原則，可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做**依賴注入**（Dependency Injection，簡稱DI），還有一種方式叫**依賴尋找**（Dependency Lookup）。*"

或許文字說明有些難懂，我們直接透過一個程式來理解較為直觀:
```java
//設計一個遊戲主機 GameStation的類別
public class GameStation{
    //建立遊戲實體
    GameImage gImage = new GameImage("太空戰士");
    
    //載入遊戲
    public void play(){
        this.gImage.load();
    }
}

//設計一個遊戲GameImage的類別
public class GameImage {
    private String title;

    public GameImage(String gTitle) {
        this.title = gTitle;
    }

    public void load(){
        System.out.println("開始遊戲>" +  this.title);
    }
}

//主程式
public class Main {
    public static void main(String[] args) {
        GameStation gameStationIns = new GameStation();
        gameStationIns.play();
    }
}

```
不難發現，GameStation是一個糟透了的發明，GameStation不僅依賴了GameImage實體，還只能玩太空戰士一個遊戲。為了修改這個問題我相信大部分的開發者都能作出下列修正:

```java
public class GameStation{
    //外部遊戲的參考
    GameImage gImage;
    
    //GameStation建構子
    public GameStation(){
        this.gImage = new GameImage();
    }
    
    //GameStation建構子 (需預載遊戲)
    public GameStation(GameImage insertGImage){
        this.gImage = insertGImage;
    }
    
    //替換 GameStation (需預載遊戲)
    public void setGameImage(GameImage insertGImage){
        this.gImage = insertGImage;
    }
    
    //載入遊戲
    public void play(){
        this.gImage.load();
    }
}

//主程式
public class Main {
    public static void main(String[] args) {
        GameImage gameImageIns = new GameImage("太空戰士");
        GameImage gameImageIns2 = new GameImage("太空戰士2");
        
        GameStation gameStationIns = new GameStation(gameImageIns);
        gameStationIns.play();
        
        gameStationIns.setGameImage(gameImageIns2);
        gameStationIns.play();
    }
}
```
如此的修正的確如"依賴注入"的基本想法一般，GameStation的依賴來源從原本的內部轉移至外部，我們現在可以玩來自外部不同的遊戲了，但這樣的修正難道就沒問題了嗎? 其實不是的，假設我今天有另一種新式的遊戲HDGameImage出現，那我GameStation還是一樣玩不了，因為事實上GameStation還是依賴了GameImage類的實體。

IoC的概念裡「高階模組不應該依賴於低階模組，兩者都該依賴抽象」，或是說「實現必須依賴抽象，而不是抽象依賴實現」，文字內的實現或是低階模組指的就是例子裡的GameImage這個類的實體，我們設計GameStation不應該去依賴GameImage這樣的實作類別，應該透過抽象。

怎麼檢視依賴行為? 其實如前段所說只要思考若需要引進一個新的實現 HDGameImage，我需不需要影響高階GameStation? 其實答案就一目了然了。

於是我們有了以下修正:
```java
//設計一個可讀取的共用介面
public interface LoadableImage{
    void load();
}

//設計一個遊戲GameImage的類別
public class GameImage implements LoadableImage{
    private String title;

    public GameImage(String gTitle) {
        this.title = gTitle;
    }

    public void load(){
        System.out.println("開始遊戲>" +  this.title);
    }
}

//設計一個HD遊戲HDGameImage的類別
public class HDGameImage implements LoadableImage{
    private String title;

    public HDGameImage(String gTitle) {
        this.title = gTitle;
    }

    public void load(){
        System.out.println("開始HD遊戲>" +  this.title);
    }
}

//設計一個遊戲主機 GameStation的類別
public class GameStation{
    //外部遊戲的參考
    LoadableImage loadableImage;

    //GameStation建構子
    public GameStation(){
        this.loadableImage = new LoadableImage(){
            public void load(){
                System.out.println("無法讀取遊戲內容");
            }
        };
    }

    //GameStation建構子 (需預載遊戲)
    public GameStation(LoadableImage lImage){
        this.loadableImage = lImage;
    }

    //替換 GameStation (需預載遊戲)
    public void setLoadableImage(LoadableImage lImage){
        this.loadableImage = lImage;
    }

    //載入遊戲
    public void play(){
        this.loadableImage.load();
    }
}

//主程式
public class Main {
    public static void main(String[] args) {
        GameImage gameImageIns = new GameImage("太空戰士");
        HDGameImage gameImageIns2 = new HDGameImage("太空戰士2");

        GameStation gameStationIns = new GameStation();
        gameStationIns.play(); // 無法讀取遊戲內容

        gameStationIns.setLoadableImage(gameImageIns);
        gameStationIns.play(); //開始遊戲>太空戰士

        gameStationIns.setLoadableImage(gameImageIns2);
        gameStationIns.play(); //開始HD遊戲>太空戰士2
    }
}

```

經過了設計之後我們的GameStation更實用且更有彈性。
希望上述例子可以讓大家理解IoC的概念以及演進，下一章節會開始研究什麼是Factory Method Pattern(工廠模式)。