1. 單一職責(zé)原則(Single Responsibility Principle)
2. 里氏替換原則(Liskov Substitution Principle)
3. 依賴倒置原則(Dependence Inversion Principle)
4. 接口隔離原則(Interface Segregation Principle)
5. 迪米特法則(Law Of Demeter)
6. 開閉原則(Open Close Principle)
7. 組合/聚合復(fù)用原則(Composite/Aggregate Reuse Principle CARP)
縮寫 | 原則名稱 | 概述 |
---|---|---|
SPR | 單一職責(zé)原則 | 每一個類應(yīng)該專注于做一件事情。 |
LSP | 里氏替換原則 | 基類存在的地方,子類是可以替換的 |
DIP | 依賴倒置原則 | 實現(xiàn)盡量依賴抽象镜雨,不依賴具體實現(xiàn)。高層模塊不應(yīng)該直接依賴于低層模塊友浸,高層模塊和低層模塊應(yīng)該同時依賴一個抽象層桑驱。 |
ISP | 接口隔離原則 | 應(yīng)當為客戶端提供盡可能小的單獨的接口,而不是提供大的總的接口脉顿。 |
LOD | 迪米特法則 | 又叫最少知識原則,一個軟件實體應(yīng)當盡可能少的與其他實體發(fā)生相互作用点寥。 |
OCP | 開閉原則 | 面向擴展開放艾疟,面向修改關(guān)閉。 |
CARP | 組合/聚合復(fù)用原則 | 盡量使用合成/聚合達到復(fù)用,少用繼承蔽莱。原則: 一個類中有另一個類的對象弟疆。 |
解析
1. 單一職責(zé)原則(Single Responsibility Principle):
見名知意无切,這個條職責(zé)的潛臺詞的就是累贤,專注做一個事规伐。單一職責(zé)原則可以降低類的復(fù)雜度米诉,一個類只負責(zé)一項職責(zé)峡碉,其邏輯肯定要比負責(zé)多項職責(zé)簡單的多找前;提高類的可讀性踊餐,提高系統(tǒng)的可維護性拷呆;變更引起的風(fēng)險降低锅劝,變更是必然的攒驰,如果遵守的好,當修改一個功能時故爵,可以顯著降低對其他功能的影響玻粪。需要說明的一點是單一職責(zé)原則不只是面向?qū)ο缶幊趟枷胨赜械模灰悄K化的程序設(shè)計诬垂,都適用此原則劲室。
2. 里氏替換原則(Liskov Substitution Principle):
將一個基類對象替換成它的子類對象,程序?qū)⒉粫a(chǎn)生任何錯誤和異常结窘。反之則不成立很洋,因為如果使用的是一個子類對象的話,那么它不一定能夠使用基類對象(子類擁有父類未擁有的函數(shù))晦鞋。
此原則是實現(xiàn)開閉原則的重要方式之一蹲缠,由于使用基類對象的地方都可以使用子類對象,因此在程序中盡量使用基類類型來定義對象悠垛,而在運行時再確定其子類類型线定,用子類對象來替換父類對象。
使用里氏替換原則時需要注意确买,子類的所有方法必須在父類中聲明斤讥,或子類必須實現(xiàn)父類中聲明的所有方法。盡量把父類設(shè)計為抽象類或者接口湾趾,讓子類繼承父類或?qū)崿F(xiàn)父接口芭商,并實現(xiàn)在父類中聲明的方法,運行時搀缠,子類實例替換父類實例铛楣,我們可以很方便地擴展系統(tǒng)的功能,同時無須修改原有子類的代碼艺普,增加新的功能可以通過增加新的子類來實現(xiàn)簸州。從大局看多態(tài)就屬于這個原則鉴竭。
示例代碼
public abstract class Phone
{
public abstract void Call();
}
interface Android{ }
interface IOS{ }
public class OnePlus : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(OnePlus)}進行通話。岸浑。搏存。。矢洲。");
}
}
public class Pixel : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(Pixel)}進行通話璧眠。。读虏。责静。。");
}
}
public class XiaoMi : Phone, Android
{
public override void Call()
{
Debug.Log($"{nameof(XiaoMi)}進行通話掘譬。泰演。。葱轩。。");
}
}
public class Apple : Phone, IOS
{
public override void Call()
{
Debug.Log($"{nameof(Apple)}進行通話藐握。靴拱。。猾普。袜炕。");
}
}
不使用里氏替換,調(diào)用call函數(shù)需要為每個類型的手機寫一個函數(shù)
public void WantToCall_0(OnePlus phone)
{
phone.Call();
}
public void WantToCall_1(Pixel phone)
{
phone.Call();
}
public void WantToCall_2(XiaoMi phone)
{
phone.Call();
}
public void WantToCall_3(Apple phone)
{
phone.Call();
}
使用里氏替換初家,只需要一個函數(shù)全部搞定偎窘,在后面的【橋接模式】會廣泛用到的
public void WantToCall_4(Phone phone)
{
phone.Call();
}
3. 依賴倒置原則(Dependence Inversion Principle):
高層模塊不應(yīng)該依賴低層模塊,二者都應(yīng)該依賴其抽象溜在;抽象不應(yīng)該依賴細節(jié)陌知;細節(jié)應(yīng)該依賴抽象。 依賴倒置原則的核心思想是面向接口編程掖肋。
采用依賴倒置原則可以減少類間的耦合性仆葡,提高系統(tǒng)的穩(wěn)定性,減少并行開發(fā)引起的風(fēng)險志笼,提高代碼的可讀性和可維護性沿盅。
什么是高層模塊,什么是低層模塊纫溃?
在項目中我們經(jīng)常會有一些數(shù)學(xué)函數(shù)庫腰涧,或者工具類(Log日志工具),這些封裝好的工具會被我們業(yè)務(wù)邏輯模塊經(jīng)常調(diào)用紊浩,那么這些工具函數(shù)庫就是高層模塊窖铡,業(yè)務(wù)邏輯模塊就是低層模塊疗锐。
什么是細節(jié),什么是抽象万伤?
細節(jié)的意思就是具體的實現(xiàn)窒悔,例如上面里氏替換中打電話的例子,在不使用里氏替換的時候需要定義4種打電話函數(shù)敌买,這就是依賴細節(jié)简珠,其中的細節(jié)就是“OnePlus ”、“Pixel ”虹钮、“XiaoMi”聋庵、“Apple ”,反之抽象就是Phone 芙粱。
簡例:華碩和微型都可使用不同型號的顯卡祭玉,反之不同型號的顯卡也可以使用在不同品牌的主板上。利用依賴倒置原則春畔,這種規(guī)則的實現(xiàn)變得很簡單也很靈活~
示例代碼
//顯卡
public interface IGraphicsCard
{
void BeginWork(IMainboard mainboard);
}
//主板
public interface IMainboard
{
void GetElectricity();
void DrawPicture(IGraphicsCard graphicsCard);
}
public class NVIDIA_2018Ti : IGraphicsCard
{
public NVIDIA_2018Ti(IMainboard mainboard)
{
BeginWork(mainboard);
}
public void BeginWork(IMainboard mainboard)
{
mainboard.GetElectricity();
Debug.Log($"NVIDIA_2018Ti獲取{mainboard.GetType()}電量后開始工作");
}
}
public class NVIDIA_2018 : IGraphicsCard
{
public NVIDIA_2018(IMainboard mainboard)
{
BeginWork(mainboard);
}
public void BeginWork(IMainboard mainboard)
{
mainboard.GetElectricity();
Debug.Log($"NVIDIA_2018獲取{mainboard.GetType()}電量后開始工作");
}
}
//華碩主板
public class Asus : IMainboard
{
public void DrawPicture(IGraphicsCard graphicsCard) { }
public void GetElectricity() { }
}
//微型主板
public class MSI : IMainboard
{
public void DrawPicture(IGraphicsCard graphicsCard) { }
public void GetElectricity() { }
}
實現(xiàn)代碼脱货,這種2*2種模式的實現(xiàn)非常簡單~
public void DrawPicture()
{
IMainboard aSus = new Asus();
aSus.DrawPicture(new NVIDIA_2018Ti(aSus));
aSus.DrawPicture(new NVIDIA_2018(aSus));
IMainboard mSI = new MSI();
mSI.DrawPicture(new NVIDIA_2018Ti(mSI));
mSI.DrawPicture(new NVIDIA_2018(mSI));
}
4. 接口隔離原則(Interface Segregation Principle):
提供盡可能小的單獨接口,而不要提供大的總接口律姨。具體行為讓實現(xiàn)類了解越少越好振峻。
盡量細化接口,接口中的方法盡量少择份。也就是要為各個類建立專用的接口扣孟,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調(diào)用。依賴幾個專用的接口要比依賴一個綜合的接口更靈活荣赶。接口是設(shè)計時對外部設(shè)定的約定凤价,通過分散定義多個接口,可以預(yù)防外來變更的擴散拔创,提高系統(tǒng)的靈活性和可維護性利诺。
通俗的講就是定義的接口盡量按照功能細分,比如打電話功能一個接口伏蚊,上網(wǎng)一個接口立轧,發(fā)短信一個接口。接口粒度小不僅職能明確躏吊,也不會因為使用某種職能而必須實現(xiàn)一些不必要的功能氛改。
示例代碼
5. 迪米特法則(Law Of Demeter)又稱【最少知識原則】:
類與類之間的關(guān)系越密切,耦合度越大比伏,只有降低類與類之間的耦合才符合設(shè)計模式胜卤;對于被依賴的類來說,無論邏輯多復(fù)雜都要盡量封裝在類的內(nèi)部赁项。
每個對象都會與其他對象有耦合關(guān)系葛躏,我們稱出現(xiàn)成員變量澈段、方法參數(shù)、方法返回值中的類為直接的耦合依賴舰攒,而出現(xiàn)在局部變量中的類則不是直接耦合依賴败富,也就是說,不是直接耦合依賴的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部摩窃。
一個對象對另一個對象知道的越少越好兽叮,即一個實體應(yīng)當盡可能少的與其他實體發(fā)生相互作用,在一個類里降低引用其他類猾愿,尤其是局部變量的依賴類鹦聪,能省則省。同時兩個類不必彼此直接通信蒂秘,那么這兩個類就不必發(fā)生直接的相互作用泽本。如果其中一個類需要調(diào)用另一個類的某一方法的話,可以通過第三者轉(zhuǎn)發(fā)這個調(diào)用姻僧。
表達的意思是能用 private规丽、protected的就不要用public,不要過多的暴露自己的內(nèi)容撇贺,而且對應(yīng)類與類之間的關(guān)系嘁捷,盡量越少越好。后面講到的門面模式和中介者模式想表達的也是這個意思显熏。
迪米特法則其根本思想,是強調(diào)了類之間的松耦合晒屎。類之間的耦合越弱喘蟆,一個處于弱耦合的類被修改,對有關(guān)類造成波及的影響越小鼓鲁。
示例代碼
注:這種情況就違背了迪米特法則蕴轨。因為其他的類不需要這個Log擴展,這也就破壞了原來的結(jié)構(gòu)骇吭,侵入性太強了橙弱。如果所有的擴展都是以O(shè)bject為基準,那么調(diào)用函數(shù)的時候就會造成下拉函數(shù)條目過多燥狰。
public static class Exted
{
public static void CustomerLog_Obj0(this GameObject Obj) { }
public static void CustomerLog_Obj1(this object Obj) { }
public static void CustomerLog_Obj2(this object Obj) { }
public static void CustomerLog_Obj3(this object Obj) { }
public static void CustomerLog_Obj4(this object Obj) { }
}
6. 開閉原則(Open Close Principle):
主要體現(xiàn)對擴展開放棘脐、對修改封閉,意味著有新的需求或變化時龙致,可以對現(xiàn)有代碼進行擴展蛀缝,以適應(yīng)新的情況。軟件需求總是變化的目代,世界上沒有一個軟件是不變的屈梁,因此對軟件設(shè)計人員來說嗤练,在不需要對原有系統(tǒng)進行修改的情況下,實現(xiàn)靈活的系統(tǒng)擴展在讶。
例如通過模板方法模式和策略模式進行重構(gòu)煞抬,實現(xiàn)對修改封閉,對擴展開放的設(shè)計思路构哺。
封裝變化革答,是實現(xiàn)開閉原則的重要手段,對于經(jīng)常發(fā)生變化的狀態(tài)遮婶,將其封裝為一個抽象蝗碎,但拒絕濫用抽象,只將經(jīng)常變化的部分進行抽象旗扑。
通俗的講在功能變動的時候蹦骑,盡量以增量補丁的形式更改,也就是原來代碼保持不變的同時進行更改臀防。
7. 組合/聚合復(fù)用原則(Composite/Aggregate Reuse Principle CARP):
整個設(shè)計模式就是在講如何合理安排類與類之間的組合/聚合眠菇。在一個新的對象里面通過關(guān)聯(lián)關(guān)系,使一些已有的對象成為新對象的一部分袱衷,新對象通過委派調(diào)用已有對象的方法捎废,達到復(fù)用其已有功能的目的。也就是致燥,要盡量使用類的合成復(fù)用登疗,不要使用繼承。
繼承復(fù)用破壞數(shù)據(jù)封裝性嫌蚤,將基類的實現(xiàn)細節(jié)全部暴露給了派生類辐益,基類的內(nèi)部細節(jié)常常對派生類是透明的。白箱復(fù)用脱吱,雖然簡單智政,但不安全,不能在程序的運行過程中隨便改變箱蝠⌒妫基類的實現(xiàn)發(fā)生了改變,派生類的實現(xiàn)也不得不改變宦搬;從基類繼承而來的派生類是固定的牙瓢,不能在運行時發(fā)生改變,因此沒有足夠的靈活性床三。
組合/聚合復(fù)用原則可以使系統(tǒng)更加靈活一罩,類與類之間的耦合度降低,一個類的變化對其他類造成的影響相對較少撇簿,因此一般首選使用組合/聚合來實現(xiàn)復(fù)用聂渊;其次才考慮繼承差购,在使用繼承時,需要嚴格遵循里氏代換原則汉嗽,有效使用繼承會有助于對問題的理解欲逃,降低復(fù)雜度,而濫用繼承反而會增加系統(tǒng)構(gòu)建和維護的難度以及系統(tǒng)的復(fù)雜度饼暑,因此需要慎重使用繼承復(fù)用稳析。
核心思想:組合優(yōu)于繼承