物件導向設計原則:SOLID

更新 發佈閱讀 37 分鐘
  • 單一職責原則(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.
中文翻譯:軟體實體應該對擴展開放,對修改關閉



◀結論:


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

如果有功能擴展變化的需求,希望是增加類而不是修改

修改會影響原有功能,引入錯誤

增加類就不會影響原有的東西

原則的原則,五大原則是手段,這個是目標


更多介紹 可以參考我的部落格


留言
avatar-img
留言分享你的想法!
avatar-img
一代軍師
8會員
39內容數
我是這個部落格的作者,喜歡分享有關投資 💰、軟體開發 💻、占卜 🔮 和虛擬貨幣 🚀 的知識和經驗。
一代軍師的其他內容
2024/02/12
盤面基本組成要素 十天干:甲乙丙丁戊己庚辛壬癸 十二地支:子丑寅卯辰巳午未申酉戌亥 五行:木、火、土、金、水 八門:休、生、傷、杜、景、死、驚、開 八神:符、蛇、陰、合、虎、武、九、天 九星:蓬、任、沖、輔、英、芮、柱、心、禽 八卦:坎、艮、震、兌、離、坤、乾 六個旬首:甲子戊、甲戊
Thumbnail
2024/02/12
盤面基本組成要素 十天干:甲乙丙丁戊己庚辛壬癸 十二地支:子丑寅卯辰巳午未申酉戌亥 五行:木、火、土、金、水 八門:休、生、傷、杜、景、死、驚、開 八神:符、蛇、陰、合、虎、武、九、天 九星:蓬、任、沖、輔、英、芮、柱、心、禽 八卦:坎、艮、震、兌、離、坤、乾 六個旬首:甲子戊、甲戊
Thumbnail
2023/10/22
Drawmind 畫鏡 主要目的是透過藝術治療,幫助個人減輕情感壓力、提升心理健康,並提供一個具有專業指導的平台,讓用戶進行情感表達、自我探索和康復。
Thumbnail
2023/10/22
Drawmind 畫鏡 主要目的是透過藝術治療,幫助個人減輕情感壓力、提升心理健康,並提供一個具有專業指導的平台,讓用戶進行情感表達、自我探索和康復。
Thumbnail
2023/08/14
原理 八字 八字不等於出生時間 八字是根據每個兩小時為一個單位的時辰來劃分的。 它包括年、月、日和時這四個要素,其中年、月、日分別對應天干地支。 即使缺少具體出生時刻,也仍然可以排出命盤進行分析。 紫微斗數 需要精確的出生的小時數,因為出生時間的稍微差異呈現出來的命盤會有天差地別,從而
Thumbnail
2023/08/14
原理 八字 八字不等於出生時間 八字是根據每個兩小時為一個單位的時辰來劃分的。 它包括年、月、日和時這四個要素,其中年、月、日分別對應天干地支。 即使缺少具體出生時刻,也仍然可以排出命盤進行分析。 紫微斗數 需要精確的出生的小時數,因為出生時間的稍微差異呈現出來的命盤會有天差地別,從而
Thumbnail
看更多
你可能也想看
Thumbnail
在小小的租屋房間裡,透過蝦皮購物平臺採購各種黏土、模型、美甲材料等創作素材,打造專屬黏土小宇宙的療癒過程。文中分享多個蝦皮挖寶地圖,並推薦蝦皮分潤計畫。
Thumbnail
在小小的租屋房間裡,透過蝦皮購物平臺採購各種黏土、模型、美甲材料等創作素材,打造專屬黏土小宇宙的療癒過程。文中分享多個蝦皮挖寶地圖,並推薦蝦皮分潤計畫。
Thumbnail
小蝸和小豬因購物習慣不同常起衝突,直到發現蝦皮分潤計畫,讓小豬的購物愛好產生價值,也讓小蝸開始欣賞另一半的興趣。想增加收入或改善伴侶間的購物觀念差異?讓蝦皮分潤計畫成為你們的神隊友吧!
Thumbnail
小蝸和小豬因購物習慣不同常起衝突,直到發現蝦皮分潤計畫,讓小豬的購物愛好產生價值,也讓小蝸開始欣賞另一半的興趣。想增加收入或改善伴侶間的購物觀念差異?讓蝦皮分潤計畫成為你們的神隊友吧!
Thumbnail
英文裡有句話說「Bend the rules」,也有人會用「Bend the law」。不管是那種用法,多是用在放寬規定,讓原本不符標準、遲遲無法合規的人,也能順利過關。我的觀察,用這句話常常是「情理法」三個字的順序有調整必要的時候。投射在職場上,這句英文我認為跟「因人設事」頗有異曲同工之妙。
Thumbnail
英文裡有句話說「Bend the rules」,也有人會用「Bend the law」。不管是那種用法,多是用在放寬規定,讓原本不符標準、遲遲無法合規的人,也能順利過關。我的觀察,用這句話常常是「情理法」三個字的順序有調整必要的時候。投射在職場上,這句英文我認為跟「因人設事」頗有異曲同工之妙。
Thumbnail
以為自己可以日更 然後馬上失敗 所以 我只好又嘗試再度逼耍廢的自己至少... 完成一篇文章吧? 食言而肥應該可以打點折 我一直都相信 能量不滅定律 類似..."錢沒有不見 只是變成喜歡的東西"這種概念?
Thumbnail
以為自己可以日更 然後馬上失敗 所以 我只好又嘗試再度逼耍廢的自己至少... 完成一篇文章吧? 食言而肥應該可以打點折 我一直都相信 能量不滅定律 類似..."錢沒有不見 只是變成喜歡的東西"這種概念?
Thumbnail
二、繼承(inheritance) 繼承就是假如A(子)類別去繼承B(父)類別,那麼A(子)類別可以直接去使用B(父)類別非私有的屬性和方法,但是A(子)只能繼承一個B(父)類別ㄛ! 一樣的道理可以比喻為:爸爸跟小孩之間的關係。小孩可以去運用爸爸的資源,但是爸爸的工作屬於他自己的不能跟小孩一起分享,
Thumbnail
二、繼承(inheritance) 繼承就是假如A(子)類別去繼承B(父)類別,那麼A(子)類別可以直接去使用B(父)類別非私有的屬性和方法,但是A(子)只能繼承一個B(父)類別ㄛ! 一樣的道理可以比喻為:爸爸跟小孩之間的關係。小孩可以去運用爸爸的資源,但是爸爸的工作屬於他自己的不能跟小孩一起分享,
Thumbnail
我不是個完美的人;即使現在也不推崇完美。 應該說,我不是那個我所想成為的人,因此,我之所以努力。 每次在唸完小孩之後,就會默默地發覺,我也還沒成為那樣的人,卻要求孩子「現在」「立刻」成為那樣的人。例如:控制情緒,而情緒根本不能,也不應該被控制。
Thumbnail
我不是個完美的人;即使現在也不推崇完美。 應該說,我不是那個我所想成為的人,因此,我之所以努力。 每次在唸完小孩之後,就會默默地發覺,我也還沒成為那樣的人,卻要求孩子「現在」「立刻」成為那樣的人。例如:控制情緒,而情緒根本不能,也不應該被控制。
Thumbnail
生活中睜開眼的第一剎那就要面臨各種選擇,建立一個準則越來越靠近我們真正想要的生活與工作。 透過原則找到幫助我們做決定!
Thumbnail
生活中睜開眼的第一剎那就要面臨各種選擇,建立一個準則越來越靠近我們真正想要的生活與工作。 透過原則找到幫助我們做決定!
Thumbnail
單一職責原則(Single Responsibility Principle) 里氏替換原則(Liskov Substitution Principle) 依賴反轉原則(Dependence Inversion Principle) 最少知識原則(得墨忒耳定律)(Law Of Demeter)
Thumbnail
單一職責原則(Single Responsibility Principle) 里氏替換原則(Liskov Substitution Principle) 依賴反轉原則(Dependence Inversion Principle) 最少知識原則(得墨忒耳定律)(Law Of Demeter)
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News