有關 控制反轉(IOC)/依賴注入(DI)


控制反轉(IOC)/依賴注入(DI)


控制反轉 (IOC Inversion of Control)

一種設計原則:主要目的是降低程式之間的耦合。

相信大家應該多少都有追劇的經驗,
像是 : 如懿傳、延禧攻略、冰與火之歌、紙牌屋、voice、鬼怪….等等的。

而大部分應該都使用一些APP或是追劇網站,
: 愛奇藝、NetflixKKTV99KUBOMOD(?)…等等

為什麼?
因為這些網站或APP都已經幫你把影片準備好。

需要的 影片 ,不用自己 準備 ,而是由 APP提供 給你。

需要的 物件,不用自己 取得,而是 介面提供 給你。

需要的 依賴實例,不用 主動建立,而是 被動接收。

舉例來說 :

今天我們要畫出一張趨勢圖給使用者,我們實作出一個Class然後開始使用他的方法。
class Program
{
    static void Main(string[] args)
    {
        RunChart chart = new RunChart();
        chart.DrawGraphics();
    }
}

class RunChart
{
    public void DrawGraphics()
    {
        .....
    }
}

那如果天才公務員,突然又跟你說 : 「不好意思,當時我自己都不知道我要什麼功能,所以現在需要新增圓餅圖可以嗎?

當然我們馬上就又生出一個版本
class Program
{
    static void Main(string[] args)
    {
        //RunChart chart = new RunChart();
        PieChart chart = new PieChart();
        chart.DrawGraphics();
    }
}

class PieChart
{
    public void DrawGraphics()
    {
        .....
    }
}

但是這樣的寫法,彈性太低如果我們能用 介面(interface) 的方式。
將實作跟功能拆開,就可以動態的選擇要觀看的圖表。

因此我們改寫成
interface DrawChart
{
    void DrawGraphics();
}

class PieChart extends DrawChart
{
    public void DrawGraphics()
    {
        .....
    }
}

class RunChart extends DrawChart
{
    public void DrawGraphics()
    {
        .....
    }
}

class Program
{
    static void Main(string[] args)
    {
        DrawChart chart;
        if (args[0] == "pie")
        {
            chart = new PieChart();
        }
        else
        {
            chart = new RunChart();
        }
        chart.DrawGraphics();
    }
}


改寫後的方式與原來的不同在於,傳統的寫法較沒彈性。在定義物件時就決定了他的使用方式。
而用了interface是根據使用方式來決定物件的型態。讓物件不再是主要建立,而是被動的接收。

就如同好萊塢原則 : 別打電話給我們,有事我會打電話給你」。


依賴注入(DI Dependency Injection)


雖然我們利用了interface改善了依賴對象,但是還是需要自己實作類別。
如果想要解除這種依賴關係有需多方法,依賴注入(DI)就是現在許多主流框架使用的一種作法。
使用的語言和框架,如 :  AngularJSReactJSDotnet CoreSpring…等等

假如我們今天要看一部愛情片,一定馬上就可以構想出架構並寫出來。

class VideoPlayer {

     private RomanticMovies movie;

    public VideoPlayer() {
        movie =new RomanticMovies();

    }
    public void Start(){
        movie.play();
    }
}

class RomanticMovies {
    public void Start(){
        ...
    }
}

OK這一切都很簡單,但是如果我突然要改看恐怖片呢?
很簡單我們把愛情片的程式複製一下,然後改一下內容讓他變成恐怖電影,然後再影片撥放器的建構子加個條件讓他來選擇要建立哪種類型的影片。

class VideoPlayer {

    private RomanticMovies romanticMovies;
    private HorrorMovies horrorMovies;
    private Boolean moviesType;

    public VideoPlayer(Boolean moviesType) {
        this.moviesType = moviesType;
        if(moviesType){
            romanticMovies=new RomanticMovies();
        }else{
            horrorMovies=new HorrorMovies();
        }

    }
    public void Start(){
        if(moviesType){
            romanticMovies.play();
        }else{
            horrorMovies.play();
        }
    }
}

class RomanticMovies {
    public void Start(){
        ...
    }
}
class HorrorMovies {
    public void Start(){
        ...
    }
}

OK問題來了如果說我要看的影片不只兩種,由於我們原本的寫法耦合性高彈性低,要針對變動部分做修改就是一直複製貼上。
導致程式碼變成一堆樣板程式。

我們來重新整理一下,如果今天我的影片撥放器很單純不管是什麼類型的影片,影片由別人來提供給我而我就只負責影片的撥放。
那麼我們就可以將程式碼改寫成下面這樣。


實際案例
class VideoPlayer {

     private Video video; // 依賴 『抽象』,而非『具體』

    // 建構子注入
    public VideoPlayer(Video video) {
        this.video = video;
    }

    public void playVideo() {
        if (video != null) {
            video.play();
        }
    }
}

interface Video
{
   .....
}


這樣的方法使得我們對於影片的依賴性降低了,這樣在程式的撰寫上或許沒有比較快但耦合性較低。

而注入的方式除了從建構子注入外還可以使用,介面注入跟設值方法注入。
介紹IOC/DI主要只是為了讓大家了解運作模式和概念,除了提高新的框架的學習速度外對於單元測試(Unit test)來說DI也是一項重要的概念。


由於第一次寫這種文章,部分參考其他文章
如有不妥麻煩告知

參考:https://notfalse.net/3/ioc-di

留言

這個網誌中的熱門文章

面試辛酸之路 上

面試辛酸之路 下

Spring 中使用 Sql2O