最近在看七牛云許式偉的架構課,?重溫了面向對象五大設計原則(SOLID),扣理論文字找出處。(當然許老板是不可能深聊這么低級的內容,🤡)
注意區分設計原則和設計模式。
設計原則更為抽象和泛化;
設計模式也是抽象或泛化的良好實踐,但是它們提供了更具體和實用的底層建議。
面向對象5大設計原則 | |
Single Responsiblity Principle | 單一職責原則 |
Open/Closed Principle | 開閉原則 |
Likov Substitution Principle | 里斯替代原則 |
Interface Segregation Principle | 接口隔離原則 |
Dependency inversion | 依賴倒置原則 |
單一職責原則
只能有一個讓組件或類發生改變的原因;或者說每個組件或類專注于單一功能,解決特定問題。
there should never be more than one reason for a class to change. A class should be focused on a single functionality, address a specific concern.
開閉原則
對擴展開放, 對修改封閉。
擴展類的幾種方式:
??從類繼承
??類中重寫同名行為
??擴展類的某些行為
一般我們通過繼承或者實現接口來實踐開閉原則。
class?Person{public?int?age;public?string?name;public?Person(string?name,?int?age){this.name?=?name;this.age?=?age;}public?virtual?void?SayHallo(){Console.WriteLine("我是{0},今年{1}",?name,?age);}}class?Student?:?Person{public?string?major;public?Student(string?name,?int?age,?string?major)?:?base(name,?age){this.major?=?major;}public?override?void?SayHallo()???//子類中override重寫,實現虛方法{Console.WriteLine("我是{0},今年{1},正在學習{2}",?name,?age,?major);}}class?Program{static?void?Main(string[]?args){Person?trevor1?=?new?Person("Trevor",?18);trevor1.SayHallo();Student?trevor2?=?new?Student("Trevor",?18,"C#");trevor2.SayHallo();}}output:
我是Trevor,今年18
我是Trevor,今年18,正在學習C#
里氏替代原則
在父子類生態中,在父類出現的地方,可以用子類對象替換父類對象,同時不改變程序的功能和正確性。
。。?乍一看,這不是理所當然嗎?
為啥單獨拎出來鞭尸,鞭策。
比如上例我們使用
Person?trevor1?=?new?Student("trevor",18,"C#")??//?子類對象替換父類對象trevor1.SayHello();
利用多態正確表達了含義。
但是某些情況下濫用繼承,卻不一定保證程序的正確性,會對使用者造成誤解。
比如下面經典的[矩形-正方形求面積]反例:
public?class?Rectangle
{//?分別設置寬高public?virtual?double?Width?{get;set;}public?virtual?double?Height?{get;set;}public?virtual?void?Area(){Console.WriteLine("面積是:"?+ Width * Height);}
}public?class?Square?:?Rectangle
{public?override?double?Width?{//?get;set???//??因為是正方形,想當然重設了寬=高{base.Width=?value;base.Height=?value;}}public?override?double?Height{//??get;set??//??因為是正方形,想當然重設了寬=高{base.Width?=?value;base.Height?=?value;}}public?override?void?Area(){Console.WriteLine("面積是:"?+ Width * Width);}?
}public??class?Program
{public?static?void?Main(){Rectangle?s?=?new?Rectangle();s.Width?=?2;??????????s.Height?=?3;?????????s.Area();}
}output:
面積是:6
但是如果你[使用子類對象去替換父類對象]:
Rectangle?s2?=?new?Square();s2.Width?=?2;??????????s2.Height?=?3;?????????s2.Area();output:
面積是:9
Get到了嗎?我們不能想當然的認為子類對象就能無損替換父類對象, 根本原因是我們正方形雖然是(is a)矩形,但是我們的重寫行為破壞了父類的表達,這是一種繼承的誤用。
里氏替代原則就是約束你在繼承(is a)的時候注意到這個現象,并提醒你規避這個問題。
這個時候,不應該重寫父類的SetWight方法, 而應該擴展新的方法SetLength。
接口隔離?
將胖接口修改為多個小接口,調用接口的代碼應該比實現接口的代碼更依賴于接口。
why:如果一個類實現了胖接口的所有方法(部分方法在某次調用時并不需要),那么在該次調用時我們就會發現此時出現了(部分并不需要的方法),而并沒有機制告訴我們現在不應該使用這部分方法。
how:
避免胖接口,不要實現違反單一職責原則的接口。可以根據實際多職責劃分為多接口,某個類實現多接口后, 在調用時以特定接口指代對象,這樣這個對象只能體現特定接口的方法,以此體現接口隔離。
public?interface?IA{void?getA();}interface?IB{void?getB();}public?class?Test?:?IA,?IB{public?string?Field?{?get;?set;?}public?void?getA(){throw?new?NotImplementedException();}public?void?getB(){throw?new?NotImplementedException();}}class?Program{static?void?Main(string[]?args){Console.WriteLine("Hello?World!");IA?a?=?new?Test();a.getA();???????//??在這個調用處只能看到接口IA的方法,?接口隔離}}
依賴倒置原則
實現依賴于抽象, 抽象不依賴于細節。
Q:這個原則我其實一開始沒能理解什么叫“倒置”?
A: 但有了一點開發經驗后開始有點心得了。
痛點:面向過程的開發,上層調用下層,上層依賴于下層。當下層變動時上層也要跟著變動,導致模塊復用度降低,維護成本增高。

提煉痛點:含有高層策略的模塊,如AutoSystem模塊,依賴于它所控制的低層的負責具體細節的模塊。
思路:找到一種方法使AutoSystem模塊獨立于它所控制的具體細節,那么我們就可以自由地復用AutoSystem了;同時讓底層汽車廠也依賴抽象,受抽象驅動,這就形成一種“倒置”。

所以依賴倒置原則有兩個關鍵體現:
①? 高層次模塊不應該依賴于低層實現,而都應該依賴于抽象;
這在上圖:AutoSystem和Car都依賴于抽象接口ICar
②? 抽象不應該依賴于具體實現,具體實現應該依賴于抽象。
第2點與第1點不是重復的,這一點意味著細節實現是受抽象驅動,這也是“倒置”的由來,?這一點是通過接口叫ICar而不是IAutoSystem來體現。
面相對象五大設計原則SOLID,是指導思想,不貫徹這5大設計原則也能讓程序跑起來,但是可能就會出現閱讀性、維護性、正確性問題。
本人會不時修正理解、更正錯誤,請適時移步左下角永久更新地址;也請看客大膽斧正。