六大設計原則【單一職責】【里氏替換】【 迪米特法則】【依賴倒置原則】【接口隔離原則】【開閉原則】

設計模式:面嚮對象語言開發過程當中,遇到種種的場景和問題,提出的解決方案和思路,沉澱下來,設計模式是解決具體問題的套路編程

設計模式六大原則:面嚮對象語言開發過程當中,推薦的一些指導性原則,這些是沒有明確的招數的,並且也常常被忽視或者違背!設計模式

 

一:單一職責原則(Single Responsibility Principle)安全

單一職責原則就是一個類只負責一件事兒,面嚮對象語言開發,類就是一個最基本的單位,單一職責的原則就是封裝的粒度,主要關注的單個類實現的功能架構

好比咱們下面的例子框架

 1 /// <summary>
 2 /// 封裝
 3 /// 動物類
 4 /// 簡單意味着穩定
 5 /// </summary>
 6 public class Animal
 7 {
 8     private string _Name = null;
 9     public Animal(string name)
10     {
11         this._Name = name;
12     }
13    
14     //應該拆分了
15     public void Action()
16     {
17         if (this._Name.Equals(""))
18             Console.WriteLine($"{this._Name} flying");
19         else if (this._Name.Equals(""))
20             Console.WriteLine($"{this._Name} walking");
21         else if (this._Name.Equals(""))
22             Console.WriteLine($"{this._Name} Swimming");
23         else if (this._Name.Equals("蚯蚓"))
24             Console.WriteLine($"{this._Name} Crawling");
25     }
26 }

咱們聲明一個動物,可是每一個動物的action是不同的,順着咱們的思惟模式,咱們會在action中增長對應的if來判斷,不一樣的動物有不一樣的動做,iphone

相似於這樣的,在一個方法中寫分支判斷,而後執行不一樣的邏輯,這就違背了單一職責原則,可是其實咱們想要的功能徹底能實現,若是種類比較少且不變的狀況下,咱們徹底能夠這樣操做,可是若是種類比較多且常常容易發生改變,那咱們這樣寫就有很大的隱患,由於其中的改變有可能會影響到其它的。分佈式

咱們能夠對其進行改變,好比咱們能夠先建立一個基類ide

 1  public abstract class AbstractAnimal
 2  {
 3      protected string _Name = null;
 4      public AbstractAnimal(string name)
 5      {
 6          this._Name = name;
 7      }
 8 
 9      public abstract void Breath();
10      public abstract void Action();
11  }

而後能夠建立不一樣動物的類來繼承於這個基類this

public class Fish : AbstractAnimal
{
    public Fish() : base("")
    {
    }

    public override void Breath()
    {
        Console.WriteLine($"{base._Name} 呼吸水");
    }
    public override void Action()
    {
        Console.WriteLine($"{base._Name} swimming");
    }
}
public class Chicken : AbstractAnimal
{
  public Chicken() : base("")
    {
    }

    public override void Breath()
    {
        Console.WriteLine($"{base._Name} 呼吸空氣");
    }
    public override void Action()
    {
        Console.WriteLine($"{base._Name} flying");
    }
}

相似於這樣的,而後在本身的類中實現本身的方法,一個類只負責本身的事情,且都比較單一,簡單意味着穩定,意味着強大,這就是所謂的單一職責原則,那麼究竟何時回使用單一職責原則呢?若是類型複雜,方法多,這樣建議使用單一職責原則!spa

那麼使用單一職責原則也有本身的弊端,具體分爲如下兩個方面

1:代碼量的增長(拆分開類的代碼明顯比以前增長)

2:使用成本就是所謂的理解成本增高(調用者要曉得不一樣的類)

具體的單一原則分爲如下五種

1:方法級別的單一職責原則:一個方法只負責一件事兒(職責分拆小方法,分支邏輯分拆)
2:類級別的單一職責原則:一個類只負責一件事兒
3:類庫級別的單一職責原則:一個類庫應該職責清晰
4:項目級別的單一職責原則:一個項目應該職責清晰(客戶端/管理後臺/後臺服務/定時任務/分佈式引擎)
5:系統級別的單一職責原則:爲通用功能拆分系統(IP定位/日誌/在線統計)

 

二: 里氏替換原則(Liskov Substitution Principle)

任何使用基類的地方,均可以透明的使用其子類,這主要是指 繼承+透明(安全,不會出現行爲不一致)

繼承:子類擁有父類的一切屬性和行爲,任何父類出現的地方,均可以用子類來代替,主要是由於:

1:父類有的,子類是必須有的(私有不繼承);若是出現了子類沒有的東西,那麼就應該斷掉繼承;、

2:子類能夠有本身的屬性和行爲,可是子類出現的地方,父類不必定能代替

3:父類實現的東西,子類就不要再寫了,(就是不要new隱藏),若是想修改父類的行爲,經過abstract/virtual

舉個例子:

 1 public class People
 2 {
 3     public int Id { get; set; }
 4     public string Name { get; set; }
 5 
 7     public void Traditional()
 8     {
 9         Console.WriteLine("仁義禮智信 溫良恭儉讓 ");
10     }
11 }
12 
13 public class Chinese : People
14 {
15     public string Kuaizi { get; set; }
16     public void SayHi()
17     {
18         Console.WriteLine("早上好,吃了嗎?");
19     }
20 
21 }
22 
23 public class Hubei : Chinese
24 {
25     public string Majiang { get; set; }
26     public new void SayHi()
27     {
28         Console.WriteLine("早上好,過早了麼?");
29     }
30 }

調用的時候:

{
    Chinese people = new Chinese();
    people.Traditional();
    people.SayHi();
}
{
    Chinese people = new Hubei();
    people.Traditional();
    people.SayHi();

}
{
    var people = new Hubei();
    people.Traditional();
    people.SayHi();
}

上面須要注意的是:若是是普通的方法,以左邊爲主,就是左邊是什麼類,就調用誰的普通方法(編譯時決定),若是是abstract或者virtual則是以右邊爲主(運行時決定),因此:父類有的方法,子類就不要再寫了,(就是不要new隱藏),若是想修改父類的方法,經過abstract/virtual來標識!

三:迪米特法則

也叫最少知道原則,就是:一個對象應該對其餘對象保持最少的瞭解,只與直接的朋友通訊。

他主要的職責就是關注類與類之間的交互,下降類與類之間的耦合,儘可能避免依賴更多的類型

舉例說明:

有一個學生類,班級類,學校類

/// <summary>
/// 學生
/// </summary>
public class Student
{
    public int Id { get; set; }
    public string StudentName { get; set; }
    public int Height { private get; set; }

    public int Salay;

    public void ManageStudent()
    {
        Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.StudentName);
    }
}
 /// <summary>
 /// 班級
 /// </summary>
 public class Class
 {
     public int Id { get; set; }
     public string ClassName { get; set; }

     public List<Student> StudentList { get; set; }


     public void ManageClass()
     {
         Console.WriteLine(" {0}Manage {1} ", this.GetType().Name, this.ClassName);
         foreach (Student s in this.StudentList)
         {
             s.ManageStudent();
         }
     }
 }
 1 /// <summary>
 2 /// 學校
 3 /// </summary>
 4 public class School
 5 {
 6     public int Id { get; set; }
 7     public string SchoolName { get; set; }
 8     public List<Class> ClassList { get; set; }
 9 
10     public void Manage()
11     {
12         Console.WriteLine("Manage {0}", this.GetType().Name);
13         foreach (Class c in this.ClassList)
14         {
15             //遵循了迪米特,school直接跟本身的朋友classList通信,而不是跟本身的朋友的朋友通信
16             c.ManageClass();
17 
18             #region 違背了迪米特法則(跟本身的朋友的朋友通信)
19             //List<Student> studentList = c.StudentList;
20             //foreach (Student s in studentList)
21             //{
22             //    Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
23             //}
24             #endregion
25 
26         }
27     }
28

如今的關係是:一個學校有多個班級,每一個班級有多個學生,如今學校想要管理學生,學校能夠直接跟班級通信,而後班級跟學生通信,這就是所謂的只與直接朋友通信,而後避免依賴更多類型(這個類型不包含:基類庫BCL--框架內置)

其實類與類之間的關係能夠總結爲:

1:縱向:繼承≈實現(最密切)

2:橫向:聚合> 組合> 關聯> 依賴(出如今方法內部)

依賴別人更少,也讓別人瞭解更少,好比咱們項目中常常中用到的一些訪問修飾符:

Private:私有
Protected:子類才能獲取到,子類才能訪問到
Internal :當前dll才能看見
Public:公開得
Protected internal 疊加要麼是子類, 要麼是相同類庫

 

其實項目中能體現迪米特法則的地方好比:三層架構(UI-BLL-DAL),還有咱們習慣建立的中間層(UI-中間層--(調用不一樣的業務邏輯進行組合)),另外還有門面模式

 另外須要注意的是:單一職責法則只關注單類的功能;迪米特法則關注的是類與類之間的聯繫

 四:依賴倒置原則(Dependence Inversion Principle)

依賴倒置原則:高層模塊不該該依賴於低層模塊,兩者應該經過抽象依賴

那何爲高層何爲底層,通常來講使用者爲高層,被調用者爲低層,具體仍是舉例說明,咱們先建立一個手機:

 public abstract class AbstractPhone
 {
     public int Id { get; set; }
     public string Branch { get; set; }
     public abstract void Call();
     public abstract void Text();
 }

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);
    }
}
View Code

接着咱們建立一個學生,而後學生玩手機:

 1 public class Student
 2 {
 3     public int Id { get; set; }
 4     public string Name { get; set; }
 5 
 6     /// <summary>
 7     /// 依賴細節  高層就依賴了底層
 8     /// </summary>
 9     /// <param name="phone"></param>
10     public void PlayiPhone(iPhone phone)
11     {
12         Console.WriteLine("這裏是{0}", this.Name);
13         phone.Call();
14         phone.Text();
15     }
16 
17     public void PlayLumia(Lumia phone)
18     {
19         Console.WriteLine("這裏是{0}", this.Name);
20         phone.Call();
21         phone.Text();
22     }
23 
24     public void PlayHonor(Honor phone)
25     {
26         Console.WriteLine("這裏是{0}", this.Name);
27         phone.Call();
28         phone.Text();
29     }
30 }

而後上面的學生就是高層,而手機就是低層,而後學生玩手機不該該直接直接寫PlayiPhone,PlayLumia,PlayHonor 等,顯然這是依賴的細節,若是這樣寫,之後增長一個手機,則要在學生類中增長了一個play方法,這樣就會使學生類反覆修改而不穩定,因此咱們通常會定義一個基類爲AbstractPhone,而後把各個型號通用的手機屬性和功能都寫在基類中,而後在學生中增長一個Play的方法以下:

1 public void Play(AbstractPhone phone)
2 {
3     Console.WriteLine("這裏是{0}", this.Name);
4     phone.Call();
5     phone.Text();
6 }

這樣的話之後增長手機的話只要繼承AbstractPhone,則在外部均可以直接調用。由於父類出現的地方均可以用子類代替。

而後有人還說這個能夠直接使用泛型來寫以下:

public void PlayT<T>(T phone) where T : AbstractPhone
{
    Console.WriteLine("這裏是{0}", this.Name);
    phone.Call();
    phone.Text();
}

這個T使用基類來進行約束,其實就等同於用父類參數類型!

調用的方法以下:

 1  {
 2      iPhone phone = new iPhone();
 3      student.PlayiPhone(phone);
 4      student.PlayT(phone);
 5      student.Play(phone);
 6  }
 7  {
 8      Lumia phone = new Lumia();
 9      student.PlayLumia(phone);
10      student.PlayT(phone);
11      student.Play(phone);
12  }
13  {
14      Honor phone = new Honor();
15      student.PlayHonor(phone);
16      student.PlayT(phone);
17      student.Play(phone);
18  }

而後這樣寫的好處主要分爲如下兩點:

1 :一個方法知足不一樣類型的參數

2:還支持擴展,只要是實現了這個抽象,不用修改Student,穩定的同時又多了擴展

這就是所謂的面向抽象編程,可是面向抽象編程只適合於通用功能,若是你想要在子類中有特殊的操做,好比想要在iphone手機新增一個其它手機沒有的方法A,而後play方法還傳入AbstractPhone這個參數,這樣是麼有辦法訪問到A,這就是非通用的,就不該該面向抽象

那何爲面向抽象,以及面向抽象的好處?

面向抽象就是:只要抽象不變,高層就不變!

面向抽象的好處:

面嚮對象語言開發,就是類與類之間進行交互,若是高層直接依賴低層的細節,細節是多變的,那麼低層的變化就致使上層的變化;若是層數多了,底層的修改會直接水波效應傳遞到最上層,一點細微的改動都會致使整個系統從下往上的修改!

而面向抽象,若是高層和低層沒有直接依賴,而是依賴於抽象,抽象通常是穩定的,那低層細節的變化擴展就不會影響到高層,這樣就能支持層內部的橫向擴展,不會影響其餘地方,這樣的程序架構就是穩定的

之後不少的設計模式都會跟抽象有關,好比咱們常常說的控制反轉IOC就是一個很好的例子!依賴倒置原則(理論基礎)---IOC控制反轉(實踐封裝)---DI依賴注入(實現IOC的手段)!

 

五: 接口隔離原則(Interface Segregation Principle)

接口隔離原則則是:客戶端不該該依賴它不須要的接口, 一個類對另外一個類的依賴應該創建在最小的接口上;

這個原則跟本身實際工做有很大的關係,總結下來能夠經過如下幾點來定義接口:

1: 既不能是大而全,會強迫實現沒有的東西,也會依賴本身不須要的東西
2 :也不能一個方法一個接口,這樣面向抽象也沒有意義的
按照功能的密不可分來定義接口,
並且應該是動態的,隨着業務發展會有變化的,可是在設計的時候,要留好提早量,避免抽象的變化
沒有標準答案,隨着業務和產品來調整的

3 :接口合併 Map--定位/搜索/導航 這種屬於固定步驟,業務細節,儘可能的內聚,在接口也不要暴露太多業務細節(就是密切相連的能夠放在一個接口來實現,而不是分多個接口實現功能)

 

六:開閉原則(Open Closed Principle)

開閉原則:對擴展開發,對修改關閉

開閉原則只是一個目標,並無任何的手段,也被稱之爲總則,其餘5個原則的建議,就是爲了更好的作到OCP, 開閉原則也是面嚮對象語言開發一個終極目標!

若是有功能增長/修改的需求能夠經過優先級考慮如下修改:A:修改現有方法---B:增長方法---C:增長類---D:增長/替換類庫

相關文章
相關標籤/搜索