C#設計模式六大原則 - 依賴倒置


依賴倒置原則(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 類
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章