[C#]solid設計原則

[C#]solid設計原則

前言

有些技術的東西,不寫下來過一段時間就記不起來比較細節的部份,有時候會跟人聊到技術,或者是面試官問到,雖然書都讀過,也會遵照著原則去做,但是真要講到概念我倒是背不太起來,所以寫個筆記下次有人跟我談到,多思考回顧一下寫過的東西,就可以幫助記憶。

導覽

  1. (Single Responsibility Principle)SRP-單一職責
  2. (Open/Closed Principle)OCP-開放封閉原則
  3. (Liskoy's Substitution Principle)LSP-里氏替換原則
  4. (Interface Segregation Principle)ISP-介面隔離原則
  5. (Dependency Inversion Principle)DIP-依賴反轉原則
  6. 結論

(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,並不是什麼技術就是最好的,有興趣了解目前業界的一窩蜂狀況,可看此連結去了解一下。

https://blog.chunfuchao.com/?p=656&variant=zh-tw