天天看點

對DIP IoC DI的了解與運用

DIP,IoC,DI基本概念

依賴倒置原則(DIP,Dependency Inverse Principle):強調系統的“高層元件”不應當依賴于“底層元件”,并且不論是“高層元件”還是“底層元件”都應當依賴于抽象。抽象不應當依賴于實作,實作應當依賴于抽象。

依賴(Dependency):元件A如果:①持有B的引用,②調用B的方法,③建立(new)B,則A對B産生依賴。

控制(Control):A依賴B,則B擁有“控制權”,因為B的某種變化可能會引起A的變化。

控制反轉(IoC,Inverse of Control):就是将控制權“往高處/上層”轉移,控制反轉是實作依賴倒置 的一種方法。

依賴注入(DI,Dependency Injection):元件通過構造函數或者setter方法,将其依賴暴露給上層,上層要設法取得元件的依賴,并将其傳遞給元件。依賴注入是實作控制反轉的一種手段。

依賴倒置原則

為了了解什麼是DIP?DIP解決了什麼問題?先看一個實際的例子,下面的AppPoolWatcher是一個事件通知類,在系統有問題時調用Notify記錄日志,日志的記錄具體由EventLogWriter來做:

class EventLogWriter
{
    public void Write(string message)
    {
        //Write to event log here
    }
}

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    EventLogWriter writer = null;

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {
        if (writer == null)
        {
            writer = new EventLogWriter();
        }
        writer.Write(message);
    }
}      

這樣的設計可以很好的解決現有的問題,但是這樣的設計卻違反了DIP。因為高層元件AppPoolWatcher依賴于底層元件EventLogWriter,高層組AppPoolWatcher的實作中有一個底層元件EventLogWriter的引用(高層元件依賴底層元件的實作,而不是依賴抽象)。

這樣做會有什麼問題?如果需求發生變化,有另一個需求:AppPoolWatcher要把某些問題日志發送email給管理者,那就要再加一個類:EmailSenderAppPoolWatcher中再加一個EmailSender類的引用。當需求再繼續加功能時,比如加一個在特定情況下發送短信的類ShortMessageSender,就需要在AppPoolWatcher中再加一個類的引用。

依賴倒置原則要求高層元件AppPoolWatcher不應當依賴底層元件的具體的實作,而應該依賴于一個簡單的抽象,這個抽象對應着具體工作的實作。

控制反轉

如何實作DIP?這就是控制反轉(IoC,Inverse of Control),上面的實作中AppPoolWatcher依賴EventLogWriter,也就是被EventLogWriter“控制”,現在要把這種控制反轉一下,看下面的實作:

DIP要求高層元件依賴一個抽象,先提供一個接口用于抽象:

public interface INofificationAction
{
    public void ActOnNotification(string message);
}      

現在改變AppPoolWatcher的設計,使其依賴抽象而不是具體實作:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {
        if (action == null)
        {
            // Here we will map the abstraction i.e. interface to concrete class 
        }
        action.ActOnNotification(message);
    }
}      

改變底層元件的實作:

class EventLogWriter : INofificationAction
{   
    public void ActOnNotification(string message)
    {
        // Write to event log here
    }
}      

現在要增加新的功能,比如發送email或者短信,他們的實作可以如下:

class EmailSender : INofificationAction
{
    public void ActOnNotification(string message)
    {
        // Send email from here
    }
}

class SMSSender : INofificationAction
{
    public void ActOnNotification(string message)
    {
        // Send SMS from here
    }
}      

通過如上的設計,就實作了控制反轉,現在高層元件不依賴底層元件的具體實作,而是依賴于抽象,底層元件也依賴抽象,這個抽象可以由高層元件定義,這樣高層元件就不被底層元件“控制”,進而實作解耦。

依賴注入

在上面AppPoolWatcher的實作中,使用了一個接口INofificationAction,但是這個接口的“執行個體化”在什麼地方做比較好呢?可以像下面這樣:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {
        if (action == null)
        {
            // Here we will map the abstraction i.e. interface to concrete class 
            writer = new EventLogWriter();
        }
        action.ActOnNotification(message);
    }
}      

但是這樣做又回到了最開始的問題:高層元件AppPoolWatcher内部仍然依賴一個具體的類EventLogWriter。怎麼做可以做到更好的解耦,當在需求發生變化的時候,增加新的功能類實作INotificationAction接口的時候可以不用修改AppPoolWatcher這個類?

這就需要用到依賴注入,依賴注入就是要把高層元件依賴的抽象與具體功能類之間的綁定移出到高層元件的實作之外,來進一步減少耦合。

依賴注入主要有三種實作方式:

1 構造函數注入(Constructor injection)

2 方法注入(Method injection)

3 屬性注入(Property injection)

構造函數注入

在高層元件的構造函數中注入依賴的類,如下:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    public AppPoolWatcher(INofificationAction concreteImplementation)
    {
        this.action = concreteImplementation;
    }

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {   
        action.ActOnNotification(message);
    }
}      

如果AppPoolWatcher要使用EventLogWriter,就可以如下使用:

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher(writer);
watcher.Notify("Sample message to log");      

這種方式适合高層元件AppPoolWatcher在整個生命周期使用同一個依賴類。

方法注入

在高層元件的方法中注入依賴的類:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    // This function will be called when the app pool has problem
    public void Notify(INofificationAction concreteAction, string message)
    {
        this.action = concreteAction;
        action.ActOnNotification(message);
    }
}      

使用如下:

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher();
watcher.Notify(writer, "Sample message to log");      

可見這種方式比使用構造函數注入的方式要靈活一些,每次隻需要在方法中傳入不同的功能類,就可以實作不同的功能。

屬性注入

屬性注入的方式如下:

class AppPoolWatcher
{
    // Handle to EventLog writer to write to the logs
    INofificationAction action = null;

    public INofificationAction Action
    {
        get
        {
            return action;
        }
        set
        {
            action = value;
        }
    }

    // This function will be called when the app pool has problem
    public void Notify(string message)
    {   
        action.ActOnNotification(message);
    }
}      

使用方式如下:

EventLogWriter writer = new EventLogWriter();
AppPoolWatcher watcher = new AppPoolWatcher();
// This can be done in some class
watcher.Action = writer;

// This can be done in some other class
watcher.Notify("Sample message to log");      

屬性注入的方式比上面的構造函數注入和方法注入都要靈活,這種方式在“依賴類的注入”和“方法調用”處在不同的子產品時會很有用。

推薦文章:

IoC/DIP其實是一種管理思想:http://coolshell.cn/articles/9949.html

作者:阿凡盧

出處:http://www.cnblogs.com/luxiaoxun/

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

繼續閱讀