依賴倒置原則(DIP)、控制反轉(IoC)、依賴注入(DI)(C#)


理解:

依賴倒置原則(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機一樣的“功能”?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM