[C#]solid設計原則
前言
有些技術的東西,不寫下來過一段時間就記不起來比較細節的部份,有時候會跟人聊到技術,或者是面試官問到,雖然書都讀過,也會遵照著原則去做,但是真要講到概念我倒是背不太起來,所以寫個筆記下次有人跟我談到,多思考回顧一下寫過的東西,就可以幫助記憶。
導覽
- (Single Responsibility Principle)SRP-單一職責
- (Open/Closed Principle)OCP-開放封閉原則
- (Liskoy's Substitution Principle)LSP-里氏替換原則
- (Interface Segregation Principle)ISP-介面隔離原則
- (Dependency Inversion Principle)DIP-依賴反轉原則
- 結論
(Single Responsibility principle)SRP-單一職責
一個類別只有一個修改的理由,如果有多個原因會來修改一個類別,就應該拆分出來
假設我們有一個專門做公佈欄的service類別,實際上我們會把公佈欄資料表的方法都會放在這個類別裡面,但是假設這個公佈欄非常複雜,有下載pdf的,也有發信和簡訊的,全部都放在這個類別就顯得責任過多了,其實我們可以把下載的放進一個類別,發信和發簡訊又拆成另一個類別。
class Bulletin
{
List<string> Get()
{
// do something
}
void Add()
{
//do something
}
HttpResponseMessage DownloadPdf()
{
//do something
}
void SendMail()
{
//do something
}
}
重構後
class Bulletin
{
List<string> Get()
{
// do something
}
void Add()
{
//do something
}
}
class DownloadHelper
{
HttpResponseMessage Pdf()
{
//do something
}
}
class SendMessageHelper
{
void Mail()
{
//do something
}
}
(Open/Closed Principle)OCP-開放封閉原則
有需求時是用擴充的,而不是直接去修改原有的code,這個可以想成如果我們是通過新增一個實做類別,或者是新增一個方法插進原有的程式碼,都算是擴充的一種,其實只要把方法或屬性拆得越小,重用的機率自然就高,就很容易用擴充的方式在修改程式碼,假設我們現在原有一個下載pdf檔案的方法
class DownloadHelper
{
HttpResponseMessage Pdf()
{
//do something
}
}
忽然接到需求修改,下載pdf需要印上公司浮水印,變成是一個變相的廣告,那我們只要新增一個印浮水印的方法,加在原本下載之前,這樣就算是滿足了擴充原則。
class DownloadHelper
{
HttpResponseMessage Pdf()
{
var logo=new Logo();
logo.StampCompany();
//do something
}
}
class Logo
{
public void StampCompany()
{
}
}
(Liskoy's Substitution Principle)LSP-里氏替換原則
對象應該在不改變正確性的前提下被子類別所替代,白話點就是如果我們實做了某個抽象類別或介面,就一定都要實做所有的方法,以免呼叫此介面或抽象類別的時候,卻發現某個方法沒有實做而發生expection或什麼事都沒有做
interface IMessageHelper
{
void WritePhoneNumber();
void WriteEmail();
void WriteMessage();
void Send();
}
class SmsService : IMessageHelper
{
void WritePhoneNumber()
{
}
void WriteEmail()
{
//因為寄簡訊不屬於Email,所以這個介面就不適合這個物件使用,而不是放著不寫這段邏輯,會導致外面不知道該不該使用這個方法
}
void WriteMessage()
{
}
void Send()
{
}
}
(Interface Segregation Principle)ISP-介面隔離原則
也就是盡量使用介面來隔離,記住一個原則是介面越小越好,在比較複雜的類別裡面,可以把介面定義為一個動作,而不是一個名詞的方式,但是注意適當使用,全部都使用介面來做的話,會造成程式碼變得比較不好追,優劣需要自行評估時機點。
(Dependency Inversion Principle)DIP-依賴反轉原則
相依於抽象或介面,不相依於細節,方便做測試,也就是邏輯應該交給呼叫端來決定,一樣以公佈欄為例,原本我們是直接物件寫死的,這樣呼叫層就決定死了是PDF下載的
void Main()
{
PdfStreamHelper stream=new PdfStreamHelper();
stream.Download();
}
class PdfStreamHelper
{
HttpResponseMessage Download()
{
// return pdf
}
}
那如果以介面為原則,就得讓PdfStreamHelper相依於介面,然後呼叫端來決定要new哪個類別
void Main()
{
IHttpStreamHelepr stream=new PdfStreamHelper();
stream.Download();
}
interface IHttpStreamHelepr
{
HttpResponseMessage Download();
}
class PdfStreamHelper : IHttpStreamHelepr
{
HttpResponseMessage Download()
{
//return pdf
}
}
如果有一天決定了要下載的是Excel,我們就可以再新增一個Excel的,然後再呼叫端改成為Excel的方式
void Main()
{
IHttpStreamHelepr stream=new ExcelStreamHelper(); //改成呼叫Excel
stream.Download();
}
interface IHttpStreamHelepr
{
HttpResponseMessage Download();
}
class PdfStreamHelper : IHttpStreamHelepr
{
HttpResponseMessage Download()
{
//return pdf
}
}
class ExcelStreamHelper : IHttpStreamHelepr
{
HttpResponseMessage Download()
{
//return Excel
}
}
其實這邊只是簡單示例了程式碼,務求簡單好懂,最重要的還是精神,但是有時候為了符合SOLID,反正會把程式碼搞得複雜,比如說如果我們一切都是介面,那在追code的時候就會變得比較不好追,所以另外還有KISS和YAGNI來反模式,所以一切還是視實際狀況而決定,我個人原則是當需求有變化而且適合使用介面的時候,或者為了要做測試的層級,才使用介面,畢竟知識很重要,但一眛的人云亦云也不一定是件好事,就像前端框架雖然非常多,但是我最後使用angular2來做前端開發,我也是angularjs、react、vue、angular2都實際動手玩看看,最後評估我目前的情境專案和member適合angular2,並不是什麼技術就是最好的,有興趣了解目前業界的一窩蜂狀況,可看此連結去了解一下。