理解:

依賴倒置原則(DIP)主程式要依賴於抽象介面,不要依賴於具體實現。高層模組不應該依賴底層模組,兩個都應該以來抽象。抽象不應該依賴細節,細節應該依賴抽象。(具體看我上一篇貼子)

依賴倒置原則是六大設計原則中的一種,它的大致意思是所有模組都應該依賴於抽象,而不是直接依賴於另一個模組。依賴倒置原則僅僅只是一個原則而已,它只是告訴了你程式應該要變成什麼樣子(模組之間依賴抽象),而並沒有具體告訴你應該怎麼做。就像是在學校,老師告訴你教室要乾淨,不要有垃圾,而具體打掃垃圾的動作老師卻並沒有告訴你,你可以選擇用掃把打掃,也可以選擇用手撿,但是最終教室要乾淨(當然,你也可以不遵守)。

控制反轉(IoC)就是遵循了依賴倒置原則的一個思想。

什麼是控制?控制就是對物件進行建立、操作、銷燬。

什麼是“反轉”(叫“轉移”更為貼切)?“反轉”的意思就是將“控制”的操作交由外部來處理,自己只管用,只管要,其他的都不管。

為什麼說控制反轉遵循了依賴倒置原則?雖然A模組需要B模組,但是A模組中並不是聲明瞭B模組物件的引用,而是聲明瞭對IB(B模組抽象)的引用,A模組真正需要的是實現了IB抽象的子類,所以A模組並不依賴於B模組,而是依賴於IB抽象。

控制反轉的大意為:如果模組A需要模組B,模組A中並不是直接控制建立模組B,而是從外部控制如何建立。例如我們將建立何種物件的控制權交由配置檔案控制,然後根據配置檔案中的資訊(程式集+型別),通過反射來獲取物件,而不是直接new物件,這也是控制反轉的一種體現。

IoC容器會連線程式中的所有模組,模組將所需物件的控制權都交由IoC容器控制,IoC容器會根據使用者配置的資訊將各個模組所需要的物件事先建立完成,然後IoC容器可以通過依賴注入(DI)的方式為模組注入所需物件(還有依賴查詢(DL)),依賴注入就是一種具體實現的手段。

依賴倒置原則、控制反轉和依賴注入並不是為程式帶來新的功能,而是使得程式中模組的耦合性降低,提高每個模組的複用性。

舉個栗子:

就拿生活中最常見的自助取款機來說一下,首先我們要擁有一張銀行卡,例如建設銀行的銀行卡CCBCard類(設計的一些屬性可能不太合理,不過重要的是瞭解思想)

//建行銀行卡
public class CCBCard
{
    //銀行卡中的錢
    public decimal Money { get; set; }

    //銀行卡名字
    public String Name { get; set; }

    public CCBCard(decimal money,String name)
    {
        this.Money = money;
        this.Name = name;
    }
}

然後我們來建立一個ATM自動取款機類,該取款機擁有取錢和存錢的功能

ATM機1.0

//自動取款機
public class ATM
{
    //建行銀行卡
    public CCBCard Card = new CCBCard(1000,"建行卡");

    //取錢
    public void SubMoney(decimal money)
    {
        //判斷餘額是否足夠
        if (Card.Money >= money)
        {
            //取錢
            this.Card.Money -= money;
            Console.WriteLine($"取錢成功,餘額{Card.Money}");
        }
        else {
            Console.WriteLine("餘額不足");
        }
    }

    //存錢
    public void AddMoney(decimal money)
    {
        //存錢
        this.Card.Money += money;
    }
}

因為這個例子是生活中非常常見的,所以大家肯定一眼就看出了不妥

此時的ATM機可是說是個非常“睿智”的ATM機了,我去取錢,而ATM機卻自帶了一張建行銀行卡,與其說是個ATM機,倒不如說只能對一個CCBCard建行卡進行存取的機器。此時ATM類完全依賴於CCBCard物件,而且CCBCard物件也是由ATM類建立的,如果CCBCard修改了,或者要換其他的卡,ATM機還要做出修改。

所以接下來我們應該將這不好的兩點改掉:

1、ATM機只能讀取單一的CCBCard卡(ATM控制CCBCard物件的建立)

2、ATM只能讀取CCBCard型別的卡(ATM完全依賴於CCBCard物件)

先來解決第一點,接下來我們改進一下,為ATM機增加建構函式,通過建構函式傳遞CCBCard物件,使得ATM機可以操作其他建行卡:

ATM機2.1:

增加建構函式,兩個方法不用變

//建行銀行卡
public CCBCard Card;

//建構函式傳入CCBCard物件
public ATM(CCBCard card)
{
    this.Card = card;
}

使用:

//銀行卡
CCBCard card = new CCBCard(1000,"建行卡");
//ATM
ATM atm = new ATM(card);
//取錢
atm.SubMoney(100);

 然後來解決第二個問題,只能存取建行卡:

此時就需要用到依賴倒置原則,讓ATM類依賴於CCBCard抽象,而不是具體的實現。如果我們想存取其他銀行卡里面的錢,必須為所有的銀行卡抽象出一個介面,然後讓所有銀行卡子類都實現這個介面。

//銀行卡介面
public interface ICard
{
    string Name { get; set; }
    decimal Money { get; set; }
}

建行卡實現該介面:

//建行銀行卡
public class CCBCard:ICard
{
    //銀行卡中的錢
    public decimal Money { get; set; }

    //銀行卡名字
    public String Name { get; set; }

    public CCBCard(decimal money,String name)
    {
        this.Money = money;
        this.Name = name;
    }
}

使得ATM機依賴於ICard介面(修改了Card屬性和建構函式),而且ATM機並不控制ICard的子類,而是將控制權交由呼叫者。這一切才合情合理啊,無論使用者插入什麼卡,該ATM機都能進行存取。這就是控制反轉,而通過建構函式傳入的ICard物件則是通過依賴注入的方式注入物件。

//建行銀行卡
public ICard Card;

//建構函式傳入ICard物件(依賴注入)
public ATM(ICard card)
{
    this.Card = card;
}

依賴注入還有其他兩種:通過介面和屬性注入

//屬性注入
public ICard Card { get; set; }
//介面注入
//該方法實現了介面
public void Inject(ICard card)
{
    this.Card = card;
}

而介面注入就是通過方法注入,此種方式會增加不必要的介面,現在基本不使用,大多為建構函式注入。

新新增一個ICBC(工商銀行)卡進行測試

//工商銀行卡
public class ICBCCard : ICard
{
    public string Name { get; set; }
    public decimal Money { get; set; }
}

測試:

//建設銀行卡
CCBCard bcard = new CCBCard(1000, "CCB");
//工商銀行卡
ICBCCard ccard = new ICBCCard()
{
    Money = 1000,
    Name = "ICBC"
};

//ATM
ATM atm1 = new ATM(bcard);
ATM atm2 = new ATM(ccard);
//取錢
atm1.SubMoney(100);

IoC容器就是專門為ATM機這種模組服務的,它就像是一個大齒輪一樣,連線所有小齒輪(模組),它運轉,整個程式便運轉,如果有些模組可能需要用到其他模組中的物件,它們並不會直接依賴,而是全都由IoC容器控制。

雖然互相需要,但是互不依賴,IoC容器會事先將ICard子類建立好,然後通過依賴注入注入到ATM機中(Unity、Spring.NET等框架都是封裝完善的IoC容器),ATM機只管接收,只管索取。ATM機是不對子類進行建立的,控制權在使用者手裡,由使用者控制ATM機操作何種銀行卡,就像你去取錢一樣,你插入什麼卡自助取款機都可以取錢,這看起來是多麼平常的一件事?很多看起來高大上的思想,都是從需求演變過來的,然後由前人一點點探索研究總結出來。

至此ATM機已經完成了,可能因為ATM機太常見了,所以我所說的一切你都可以想到(換卡,換不同銀行的銀行卡),就像是在說廢話,如果你都理解了,那麼根據ATM機,你應該仔細的思索一下,你所設計的類和模組滿不滿足像ATM機一樣的“功能”?

&n