最近在看許世偉的架構課, 物件導向五大設計原則(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)矩形,但是我們的重寫行為破壞了父類別的表達,這是一種繼承的誤用。
里氏替代原則就是約束你在繼承的時候注意到這個現象,並提醒你規避這個問題。
這個時候,不應該重寫父類別的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點不是重複的,這一點意味著細節實現是受抽象驅動,這也是「倒置」的由來。
五大設計原則SOLID,是指導思想,不貫徹這5大設計原則也能讓程式跑起來,但是可能就會出現可閱讀性、維護性、正確性問題。
本文來自部落格園,作者:{有態度的馬甲},轉載請註明原文連結:https://www.cnblogs.com/JulianHuang/p/16737156.html
歡迎關注我的原創技術、職場公眾號, 加好友談天說地,一起進化