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

  • 2019 年 10 月 3 日
  • 筆記

理解:

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