- 單一職責原則(Single Reponsibility Principle,SRP)
- 里氏替換原則(Liskov Substitution Principle,LSP)
- 依賴倒置原則(Dependence Inversion Principle,DIP)
- 接口隔離原則(Interface Segregation Principe,ISP)
- 迪米特法則(Law of Demeter,LOD)
- 開閉原則(Open Closed Principle,OCP)
依賴倒置原則(DIP)
Dependence Inversion Principle,簡稱:DIP。
高層模塊不應該依賴低層模塊,兩者都應該依賴其抽象,不要依賴細節
定義:高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。
問題由來:類 classA 直接依賴類 classB ,假如要將類 classA 改爲依賴類 classC ,則必須通過修改類 classA 的代碼來達成。這種場景下,類 classA 一般是高層模塊,負責複雜的業務邏輯;類 classB 和類 classC 是低層模塊,負責基本的原子操作;如若修改類 classA ,會給程序帶來不必要的風險。
解決方案:將類 classA 修改爲依賴接口 Interface,類 classB 和類 classC 各自實現接口 Interface ,類 classA 通過接口 Interface 間接與類 classB 或者類 classC 發生聯繫,則會大大降低修改類 classA 的機率。
在C#中,抽象就是指接口或者抽象類,兩者都不能直接進行實例化;細節就是實現類,就是實現了接口或繼承了抽象類而產生的類就是實現類,可以直接被實例化。所謂的高層與低層,每個邏輯實現都是由原始邏輯組成,原始邏輯就屬於低層模塊,像我們常說的三層架構,業務邏輯層相對數據層,數據層就屬於低層模塊,業務邏輯層就屬於高層模塊,是相對來說的。
依賴倒置原則就是程序邏輯在傳遞參數或關聯關係時,儘量引用高層次的抽象,不使用具體的類,即使用接口或抽象類來引用參數,聲明變量以及處理方法返回值等。
這樣就要求具體的類就儘量不要有多餘的方法,否則就調用不到。說簡單點,就是“面向接口編程”。
論題:依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,降低並行開發引起的風險,提高代碼的可讀性。
一、舉個栗子
還是舉一個學生使用手機的例子吧,代碼如下:
學生類:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
/// <summary>
/// 依賴抽象
/// </summary>
/// <param name="phone"></param>
public void Play(AbstractPhone phone)
{
Console.WriteLine("這裏是{0}", this.Name);
phone.Call();
phone.Text();
}
}
手機抽象類:
//手機抽象類
public abstract class AbstractPhone
{
public int Id { get; set; }
public string Branch { get; set; }
public abstract void Call();
public abstract void Text();
}
蘋果手機類,繼承手機抽象類 AbstractPhone
//蘋果手機 繼承 AbstractPhone
public class iPhone : AbstractPhone
{
//打電話
public override void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
//發短信
public override void Text()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
}
榮耀手機類,同樣繼承手機抽象類 AbstractPhone
//榮耀手機 繼承 AbstractPhone
public class Honor : AbstractPhone
{
//打電話
public override void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
//發短信
public override void Text()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
}
調用程序:
class Program
{
static void Main(string[] args)
{
try
{
Student student = new Student()
{
Id = 66,
Name = "柯南"
};
{
AbstractPhone phone = new iPhone();
student.Play(phone);
}
{
AbstractPhone honor = new Honor();
student.Play(honor);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
}
該例子中,到目前爲止,項目沒有任何問題。我們常說“危難時刻見真情”,把這句話移植到技術上就成了“變更才顯真功夫”,業務繡球變更永無休止,技術前進永無止境,在發生變更時才能發覺我們的設計或程序是否是松耦合。
因此,以上項目中,手機類的聲明都以做抽象處理,聲明學生實例時依賴於細節,想用iphone還是honor,只需修改new後方的細節即可,它對低層模塊的依賴都建立在抽象上了。
然而,倘若一位老師想要打電話發短信,那此處得再寫一個Teacher類,而Teacher類的Play()方法和Student代碼出現冗餘,因此增加了代碼的不穩定性。
因此,此處的Student類模塊可以參考於手機類的接口一樣設計,新增加Person抽象類和Teacher實體類,修改之後,代碼如下:
public abstract class Person
{
public int Id { get; set; }
public string Name { get; set; }
public abstract void Play(AbstractPhone phone);
}
public class Student : Person
{
/// <summary>
/// 依賴抽象
/// </summary>
/// <param name="phone"></param>
public override void Play(AbstractPhone phone)
{
Console.WriteLine("這裏是{0}{1}", this.Name, this.GetType().Name);
phone.Call();
phone.Text();
}
}
public class Teacher: Person
{
/// <summary>
/// 依賴抽象
/// </summary>
/// <param name="phone"></param>
public override void Play(AbstractPhone phone)
{
Console.WriteLine("這裏是{0}{1}", this.Name, this.GetType().Name);
phone.Call();
phone.Text();
}
}
調用程序:
class Program
{
static void Main(string[] args)
{
try
{
Person teacher = new Teacher()
{
Id = 66,
Name = "柯南"
};
{
AbstractPhone phone = new iPhone();
teacher.Play(phone);
}
{
AbstractPhone honor = new Honor();
teacher.Play(honor);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.Read();
}
}
這樣就實現了代碼的去細節。
同時,負責Student類與負責Teacher類的開發人員,就可以獨立開發了,而且項目之間的單元測試也可以獨立地運行,而TDD(Test-Driven Development,測試驅動開發)開發模式就是依賴倒置原則的最高級應用。
二、衍生思考
依賴倒置原則的應用可以很多,其中一種的修改即使等式的右方
- 在 AbstractPhone phone = new Honor()的等號右方還可以以其他方式生成,通過 反射+工廠模式,從而實現依賴注入
- 在 .Net Core 中,使用泛型主機 (IHostBuilder)時,就用到了類型注入 Startup 類