標籤:功能 需要 .net appear 代碼 就會 電子郵件 改進 tco
1 OO的設計原則
採用物件導向的分析和設計思想,為我們分析和解決問題提供了一種全新的思維方式。我們在拿到需求之後(略去OOA,以後補全),接下來的問題就是:如何對系統進行物件導向的設計呢?
按照軟體工程的理論,物件導向的設計要解決的核心問題就是可維護性和可複用性。尤其是可維護性,它是影響軟體生命週期重要因素,通常情況下,軟體的維護成本遠遠大於初期開發成本。
一個可維護性很差的軟體設計,人們通常稱之為“臭味”的,形成的原因主要有這麼幾個:過於僵硬、過於脆弱、複用率低或者黏度過高。相反,一個好的系統設計應該是靈活的、可擴充的、可複用的、可插拔的。在20世紀80到90年代,很多業內專家不斷探索麵向對象的軟體設計方法,陸續提出了一些設計原則。這些設計原則能夠顯著地提高系統的可維護性和可複用性,成為了我們進行物件導向設計的指導原則:
1、 單一職責原則SRP
每一個類應該只專註於做一件事。
2、 “開-閉”原則OCP
每一個類應該是對擴充開放,對修改關閉。
3、 裡氏代換原則LSP
避免造成衍生類別的方法非法或退化,一個基類的使用者應當不需要知道這個衍生類別。
4、 依賴倒轉原則DIP
用依賴於介面和抽象類別來替代依賴容易變化的具體類。
5、 介面隔離原則ISP
應當為客戶提供儘可能小的介面,而不是提供大的介面。
其中,“開-閉”原則是物件導向的可複用設計的基石,其他設計原則是實現“開-閉”原則的手段和工具。
我會為大家一一進行講解。
2 單一職責原則SRP(Single-Responsibility Principle)2.1 什麼是單一職責
單一職責就是指一個類應該專註於做一件事。現實生活中也存在諸如此類的問題:“一個人可能身兼數職,甚至於這些職責彼此關係不大,那麼他可能無法做好所有職責內的事情,所以,還是專人專管比較好。”我們在設計類的時候,就應該遵循這個原則:單一職責。
我們以計算機編程為例:
在有些人眼裡,計算機就是一件東西,是一個整體,所以它把這個需求進行了抽象,最終設計為一個Calculator類,代碼如下:
class Calculator{
public String calculate() {
Console.Write("Please input the first number:");
String strNum1 = Console.ReadLine();
Console.Write(Please input the operator:");
String strOpr= Console.ReadLine();
Console.Write("Please input the second number:");
String strNum2 = Console.ReadLine();
String strResult = "";
if (strOpr == "+"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) + Convert.ToDouble(strNum2));
}
else if (strOpr == "-"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) - Convert.ToDouble(strNum2));
}
else if (strOpr == "*"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) * Convert.ToDouble(strNum2));
}
else if (strOpr == "/"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) / Convert.ToDouble(strNum2));
}
Console.WriteLine("The result is " + strResult);
}
}
另外,還有一部分人認為:計算機是一個外殼和一個處理器的組合。
class Appearance{
public int displayInput(String &strNum1,String &strOpr, String &strNum2) {
Console.Write("Please input the first number:");
strNum1 = Console.ReadLine();
Console.Write(Please input the operator:");
strOpr= Console.ReadLine();
Console.Write("Please input the second number:");
strNum2 = Console.ReadLine();
return 0;
}
public String displayOutput(String strResult) {
Console.WriteLine("The result is " + strResult);
}
}
class Processor{
public String calculate(String strNum1,String strOpr, String strNum2){
String strResult = "";
if (strOpr == "+"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) + Convert.ToDouble(strNum2));
}
else if (strOpr == "-"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) - Convert.ToDouble(strNum2));
}
else if (strOpr == "*"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) * Convert.ToDouble(strNum2));
}
else if (strOpr == "/"){
strResult = Convert.ToString(Convert.ToDouble(strNum1) / Convert.ToDouble(strNum2));
}
return strResult;
}
}
為什麼這麼做呢?因為外殼和處理器是兩個職責,都是很容易發生需求變動的因素,所以把他們放到一個類中,違背了單一職責原則。
比如,使用者可能對計算機提出以下要求:
第一,目前已經實現了“加法”、“減法”、“乘法”和“除法”,以後還可能出現“乘方”、“開方”等很多運算。
第二,現在人機介面太簡單了,還可能做個Windows計算機風格的介面或者Mac計算機風格的介面。
所以,把一個類Calculator 拆分為兩個類Appearance和Processor,更容易應對需求變化。如果介面需要修改,那麼就去修改Appearance類;如果處理器需要修改,那麼就去修改Processor類。
我們再舉一個郵件的例子。我們平常收到的郵件內容,看起來是一封信,實際上內部有兩部分組成:郵件標頭和郵件體。電子郵件的編碼要求符合RFC822標準。
第一種設計方式是這樣:
interface IEmail {
public void setSender(String sender);
public void setReceiver(String receiver);
public void setContent(String content);
}
class Email implements IEmail {
public void setSender(String sender) {// set sender; }
public void setReceiver(String receiver) {// set receiver; }
public void setContent(String content) {// set content; }
}
這個設計是有問題的,因為郵件標頭和郵件體都有變化的可能性。
1、郵件標頭的每一個域的編碼,可能是BASE64,也可能是QP,而且域的數量也不固定。
2、郵件體中封裝的郵件內容可能是PlainText類型,也可能是HTML類型,甚至於流媒體。
所謂第一種設計方式違背了單一職責原則,裡面封裝了兩種可能引起變化的原因。
我們依照單一職責原則,對其進行改進後,變為第二種設計方式:
interface IEmail {
public void setSender(String sender);
public void setReceiver(String receiver);
public void setContent(IContent content);
}
interface IContent {
public String getAsString();
}
class Email implements IEmail {
public void setSender(String sender) {// set sender; }
public void setReceiver(String receiver) {// set receiver; }
public void setContent(IContent content) {// set content; }
}
有的資料把單一職責解釋為:“僅有一個引起它變化的原因”。這個解釋跟“專註於做一件事”是等價的。如果一個類同時做兩件事情,那麼這兩件事情都有可能引起它的變化。同樣的道理,如果僅有一個引起它變化的原因,那麼這個類也就只能做一件事情。
2.2 單一職責原則的使用
單一職責原則的尺度如何掌握呢?我怎麼能知道該拆分還是不應該拆分呢?原則很簡單:需求決定。如果你所需要的計算機,永遠都沒有外觀和處理器變動的可能性,那麼就應該把它抽象為一個整體的計算機;如果你所需要的計算機,外殼和處理器都有可能發生變動,那麼就必須把它拆離為外殼和處理器。只能有一個原因可能引起計算機的變化。
單一職責原則把相同的職責進行彙總,避免把相同的職責分散到不同的類之中,這樣就可以控制變化,把變化限制在一個地方,防止因為一個地方的變動,引起更多地方的變動的“漣漪效應”。單一職責原則實際上消除了對象之間的耦合,避免一個類承擔過多的職責。單一職責不是說一個類就只有一個方法,而是單一功能。
我們在使用單一職責原則的時候,牢記以下幾點:
A、一個設計合理的類,應該僅有一個可以引起它變化的原因,即單一職責,如果有多個原因可以引起它的變化,就必須進行分離;
B、在沒有需求變化徵兆的情況下,應用SRP或其他原則是不明智的,因為這樣會使系統變得很複雜,系統由一堆細小的顆粒組成,這純屬於沒事找抽;
C、在需求能夠預計或實際發生變化時,就應該使用SRP原則來重構代碼,有經驗的設計師、架構師對可能出現的需求變化很敏感,設計上就會具有前瞻性。
.NET 進階架構師0005 架構師之路(4)---物件導向的設計原則