- 單一職責原則(Single Responsibility Principle)
- 里氏替換原則(Liskov Substitution Principle)
- 依賴反轉原則(Dependence Inversion Principle)
- 介面隔離原則(Interface Segregation Principle)
- 最少知識原則(得墨忒耳定律)(Law Of Demeter)
- 開放封閉原則(Open Closed Principle)
單一職責原則(Single Responsibility Principle)
原文定義:A class should have only one reason to change.
中文翻譯:一個模組應有且只有一個理由會使其改變。
類T負責兩個不同的職責:職責P1,職責P2。當由於職責P1需求發生改變而需要修改類T時,有可能會導致原本運行正常的職責P2功能發生故障。
👀範例程式碼:
public class SRPShow
{
public static void Show()
{
{
Animal animal = new AnimalChicken("雞");
animal.Breath();
animal.Action();
}
{
Animal animal = new AnimalFish("魚");//呼吸水
animal.Breath();
animal.Action();
}
}
}
/// <summary>
/// 封裝
/// 動物類別
/// 簡單意味者穩定
///
/// 都塞在一起,多個職責都在一起
/// </summary>
public class Animal
{
protected string _Name = null;
public Animal(string name)
{
this._Name = name;
}
public void Breath()
{
if (this._Name.Equals("雞"))
{
Console.WriteLine($"{this._Name} 呼吸空气");
}
else if (this._Name.Equals("鱼"))
{
Console.WriteLine($"{this._Name} 呼吸水");
}
}
//好多分支,好多固定分支,好多if else 三套東西
//違背單一職責:一個類只做一件事,,一個方法也應該只做一件事
public void Action()
{
if (this._Name.Equals("雞"))
{
Console.WriteLine($"{this._Name} flying");
}
else if (this._Name.Equals("魚"))
{
Console.WriteLine($"{this._Name} swimming");
}
}
}
一個類只負責一件事,拆分之後,職責變的單一
👀範例程式碼:
public class SRPShow
{
public static void Show()
{
{
Animal animal = new AnimalChicken("雞");
animal.Breath();
animal.Action();
}
{
Animal animal = new AnimalFish("魚");//呼吸水
animal.Breath();
animal.Action();
}
}
}
public abstract class Animal
{
protected string _Name = null;
public Animal(string name)
{
this._Name = name;
}
public abstract void Breath();
public abstract void Action();
}
/// <summary>
/// 每個類都在做自己的事
/// </summary>
public class AnimalChicken : Animal
{
public AnimalChicken(string name) : base(name)
{
}
public override void Breath()
{
Console.WriteLine($"{this._Name} 呼吸空气");
}
public override void Action()
{
Console.WriteLine($"{this._Name} flying");
}
}
/// <summary>
/// 封裝
/// 動物類別
/// 簡單意味者穩定
/// </summary>
public class AnimalFish : Animal
{
public AnimalFish(string name) : base(name)
{
}
public override void Breath()
{
Console.WriteLine($"{this._Name} 呼吸水");
}
public override void Action()
{
Console.WriteLine($"{this._Name} swimming");
}
}
⏯優點:
閱讀簡單,易於維護;
擴展升級時,減少修改,直接增加類別即可;
方便代碼重複使用;
⏩缺點:
單一職責成本:類別變多了;上端需要了解更多的類別
◀結論:
衡量着使用:如果類(class)相對穩定,擴充變化少,而且邏輯簡單,違背單一職責也沒關係
如果不同職責,總是一起變化,這總是一定要分開的
代碼足夠簡單,就可以稍稍違背
各使用情境:
方法:方法多個分支,還可能擴充變化,最好拆分成多個方法
類(Class):可以將類別依據功能拆分出:接受輸入-數據驗證-邏輯計算--數據庫操作--日誌(Log),為了重用,方便維護升級
介面(interface):把不同功能介面,獨立開來
類別庫:把項目拆分成多個類別庫,重用--方便維護--
項目:一個web解决所有問題,建議可以拆分例如:客戶端;管理後臺;定時服務;遠程接口;
系統:成熟的互聯網企業,有N多項目,有很多重复功能,IP庫/日誌庫/監控系統/在線統計。。。
里氏替換原則(Liskov Substitution Principle)
原文定義:Subtypes must be substitutable for their base types.
中文翻譯:子類別必須要能取代它的父類別。
白話文:父類別出現的地方,子類別就能代替它,而且要能做到替換而不出現任何錯誤或異常。
▶情境:
👀範例程式碼:
public class People
{
public int Id { get; set; }
public string Name { get; set; }
public void Traditional()
{
Console.WriteLine("仁義禮智信 溫良恭儉讓");
}
public void Show()
{ }
}
public class Chinese : People
{
public string Kuaizi { get; set; }
public void SayHi()
{
Console.WriteLine("早上好,吃了嗎?");
}
}
/// <summary>
/// 擁有大部分的屬性和行為,但是Traditional没有
/// </summary>
public class Japanese : People
{
public int Id { get; set; }
public string Name { get; set; }
public new void Traditional()
{
throw new Excrption();
}
public void Ninja()
{
Console.WriteLine("忍者精神");
}
}
因為Japanese在執行Traditional的時候會跳錯誤 因此這是沒有符合里氏替換原則
public class LSPShow
{
public static void Show()
{
{
Chinese people = new Chinese();
people.Traditional();
}
{
Chinese people = new Japanese();
people.Traditional();
}
}
}
🎦測驗:
問題:『A~F』分別是執行子類別還是父類別
ParentClass instance = new ChildClass();
ChildClass child = new ChildClass();
Console.WriteLine("下面是instance.CommonMethod()");
instance.CommonMethod();//『A』
typeof(ParentClass).GetMethod("CommonMethod").Invoke(instance, null);//『B』
typeof(ChildClass).GetMethod("CommonMethod").Invoke(instance, null);//『C』
dynamic dInstance = new ChildClass();
dInstance.CommonMethod();//『D』
Console.WriteLine("下面是instance.VirtualMethod()");
instance.VirtualMethod();//『E』
Console.WriteLine("下面是instance.AbstractMethod()");
instance.AbstractMethod();//『F』
public abstract class ParentClass
{
/// <summary>
/// CommonMethod
/// </summary>
public void CommonMethod()
{
Console.WriteLine("ParentClass CommonMethod");
}
/// <summary>
/// virtual 虛方法 必須包含實作 但是可以被重載
/// </summary>
public virtual void VirtualMethod()
{
Console.WriteLine("ParentClass VirtualMethod");
}
public virtual void VirtualMethod(string name)
{
Console.WriteLine("ParentClass VirtualMethod");
}
public abstract void AbstractMethod();
}
public class ChildClass : ParentClass
{
/// <summary>
/// new 隱藏父類
/// </summary>
public new void CommonMethod()
{
Console.WriteLine("ChildClass CommonMethod");
}
/// <summary>
/// virtual 可以被複寫
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override void VirtualMethod()
{
Console.WriteLine("ChildClass VirtualMethod");
base.VirtualMethod();
}
public sealed override void AbstractMethod()
{
Console.WriteLine("ChildClass AbstractMethod");
}
}
答案:
『A』父類=普通方法由左邊决定,編譯時决定
『B』父類
『C』子類
『D』子類=運行時決定
『E』子類=虛方法由右邊决定,運行時決定
『F』子類=抽象方法由右邊决定,運行時決定
◀結論:
里氏替換原則:任何使用父類別的地方,都可以透明的使用其子類;繼承+不改變行為
繼承:通過繼承,子類別擁有父類別的一切屬性和行為,任何父類出現的地方,都可以用子類來代替
- 子類必須完全實作父類別所有的方法,如果子類沒有父類的某項東西,就斷掉繼承;
- 子類可以有父類沒有的東西,所以子類的出現的地方,不一定能用父類來代替;
- 透明,就是安全,父類的東西換成子類后不影響程序
- 父類已經實做的東西,子類不要去new
- 父類已經實做的東西,想改的话,就必須用virtual+override 避免埋雷
宣告變數、參數、屬性、字段,最好都是基于父類的
最少知識原則(得墨忒耳定律)(Law Of Demeter)
原文定義:Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
中文翻譯:每個單元應該對其他單元只能有有限的知識:只了解跟目前單元比較親近的單元。
白話文: 單元與單元之間是會有耦合的。這個原則在告訴我們該如何控制耦合的程度。
▶情境:
如果要找學生資料
兩種做法:
A=班級管理自己的學生
B=學校管理 =如果足夠簡單,B作法也沒問題
👀範例程式碼:
/// <summary>
/// 學校
/// </summary>
public class School
{
public int Id { get; set; }
public string SchoolName { get; set; }
public List<Class> ClassList { get; set; }
public void Manage()
{
Console.WriteLine("Manage {0}", this.GetType().Name);
foreach (Class c in this.ClassList)
{
Console.WriteLine(" {0}Manage {1} ", c.GetType().Name, c.ClassName);
c.Manage();//『A』
//List<Student> studentList = c.StudentList;//『B』
//foreach (Student s in studentList)
//{
// Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
//}
}
}
}
/// <summary>
/// 班級
/// </summary>
public class Class
{
public int Id { get; set; }
public string ClassName { get; set; }
public List<Student> StudentList { get; set; }
public void Manage()
{
foreach (Student s in this.StudentList)
{
Console.WriteLine(" {0}Manage {1} ", s.GetType().Name, s.StudentName);
}
}
}
/// <summary>
/// 學生
/// </summary>
public class Student
{
public int Id { get; set; }
public string StudentName { get; set; }
protected int Tall { get; set; }
private int Salary { get; set; }
}
◀結論:
得墨忒耳定律(最少知識原則):一個物件應該對其物件保持最少的了解。
物件導向=類別=類別與類別之間會有交互=功能模塊=系統
我們在寫程式的時候會希望類跟類之間是高內聚,低耦合。
高內聚,低耦合:高度封裝,盡量少的暴露訊息,類別與類別之間減少依賴
偶合關係:【依賴】(間接朋友 ) 【關聯、組合、聚合】(直接朋友) 繼承 實現
我們可以盡量減少與間接朋友的關係,只與直接的朋友聯繫
去掉內部的依賴--降低訪問修飾符(public、private、property)
門面(外觀)模式/中介者模式
依賴反轉原則(Dependence Inversion Principle)
原文定義:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
中文翻譯:
- 高層模組不應該依賴底層模組,它們都應該依賴抽象。
- 抽象不應該依賴細節。細節應該依賴抽象。
▶情境:
依賴細節範例=學生每使用一台新手機就必須再增加一個方法
//Student高層--手機低層
Student student = new Student()
{
Id = 191,
Name = "老司機"
};
AbstractPhone phone = new iPhone();
student.PlayiPhone(phone);
AbstractPhone phone = new Lumia();
student.PlayLumia(phone);
```
```csharp=
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
/// <summary>
/// 依賴細節
/// </summary>
/// <param name="phone"></param>
public void PlayiPhone(iPhone phone)
{
Console.WriteLine("這裡是{0}", this.Name);
phone.Call();
phone.Text();
}
/// <summary>
/// 依賴細節
/// </summary>
/// <param name="phone"></param>
public void PlayLumia(Lumia phone)
{
Console.WriteLine("這裡是{0}", this.Name);
phone.Call();
phone.Text();
}
}
將手機抽離出抽象,這樣每次增加一支新手機只需要繼承AbstractPhone 而不用修改到Student
public abstract class AbstractPhone
{
public int Id { get; set; }
public string Branch { get; set; }
public abstract void Call();
public abstract void Text();
}
/// <summary>
/// iPhone
/// </summary>
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);
}
}
public class Lumia : AbstractPhone
{
public override void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
public override void Text()
{
Console.WriteLine("User {0} Text", this.GetType().Name);
}
}
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();
}
}
◀結論:
依賴反轉原則:高層模組不應該依賴底層模組,兩者應通過抽象依賴
依賴抽象,而不是依赖細節
抽象:抽象類/介面
細節:具體的類別
23種設計模式,80%以上跟這個有關
依賴細節:程式寫死了,無法進行擴展
依賴抽象,更具有通用性;而且具備擴展性;
細節多變的,抽象是穩定的;系統架構基於抽象來搭建,會更穩定更具被擴展性
面向抽象編程, 底層模組裡面盡量都有抽象類/介面,
在宣告參數/變數/屬性的時候,盡量都是 介面/抽象類
如果需求不考慮擴展變化,那確實不需要依賴反轉
介面隔離原則(Interface Segregation Principle)
原文定義:Clients should not be forced to depend on methods that they do not use.
中文翻譯:客戶不應該被強迫依賴他們不使用的方法
▶情境: public abstract class AbstractPhone
{
public int Id { get; set; }
public string Branch { get; set; }
public abstract void Call();
public abstract void Text();
public void Show()
{ }
}
public interface IExtend
{
void Movie();
}
public interface IExtendGame
{
void Online();
void Game();
}
public interface IExtendPhotography//: IExtendGame 介面可以繼承介面 方法疊加
{
void Photo();
void Record();
}
public interface IExtendAdvanced
{
void Show();
void Map();//導航
void Pay();
}
/// <summary>
/// iPhone
/// </summary>
public class iPhone : AbstractPhone, IExtend, IExtendAdvanced, IExtendPhotography
{
//net框架有时候明明看到已经继承了某个接口,却找不到实现在哪里
//實現在父類別
public override void Call()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
public override void Text()
{
Console.WriteLine("User {0} Call", this.GetType().Name);
}
public void Photo()//隐式实现
{
Console.WriteLine("User {0} Photo", this.GetType().Name);
}
//void IExtendAdvanced.Photo()//显示实现
//{
// Console.WriteLine("User {0} Photo", this.GetType().Name);
//}
public void Online()
{
Console.WriteLine("User {0} Online", this.GetType().Name);
}
public void Game()
{
Console.WriteLine("User {0} Game", this.GetType().Name);
}
public void Map()
{
Console.WriteLine("User {0} Map", this.GetType().Name);
}
public void Pay()
{
Console.WriteLine("User {0} Pay", this.GetType().Name);
}
public void Record()
{
Console.WriteLine("User {0} Record", this.GetType().Name);
}
public void Movie()
{
Console.WriteLine("User {0} Movie", this.GetType().Name);
}
}
◀結論:
介面隔離原則:客戶端不應該依賴他不需要的介面;
一個類別對另一個類別的依賴應該建立在最小的介面上;
實作一個介面的時候,只需要自己必須的的功能;
實作介面,就必須把介面裡面全部方法實作
其實就是一種約束,肯定包含具體實作的
可以多重實作,所以可以拆分,然後多個實作
介面是描述能幹什麼,要求Photography,那麼你的對象就是全部可以Photography的
那就是實作這個IExtendPhotography最小介面的
為什麼不建立一個大而全的介面,而是要拆分? 因為不能讓類型實現自己沒有的功能
為什麼要盡量的用同一個介面?盡量抽象更具被重用性
介面拆分是隨者需求更新而演變的,一直演變下去,是不是直接拆分成一個介面一個方法得了??
如果真的一个介面一个方法,那也沒意義了。
究竟該如何設計呢? 確實需要豐富的經驗和對需求的了解
1 介面不能太大,否則會實現不需要的功能;
2 介面還是要盡量的小,但是一致的功能是應該在一起的,密不可分的功能不要分開
不能過度設計 要考慮清楚業務的邊界
3 介面合併:如果一個業務包含多個固定步驟,我們不應該把步驟都暴露,而是提供一個入口即可
開放封閉原則(Open Closed Principle)
原文定義:Software entities (class, modules, functions, etc.) should be open for extension, but closed for modification.
中文翻譯:軟體實體應該對擴展開放,對修改關閉
◀結論:
開閉原則:對擴展開發,對修改關閉
如果有功能擴展變化的需求,希望是增加類而不是修改
修改會影響原有功能,引入錯誤
增加類就不會影響原有的東西
原則的原則,五大原則是手段,這個是目標