前言
既然在Day10 剛好有提到單一職責原則,這裡就來簡單說明。
「單一職責」原則顧名思義,就是一個類別應該只負責一個職責(是SOLID原則其中一個原則)。
SOLID 是物件導向設計中的五個重要原則,這些原則旨在幫助開發人員創建更容易維護、擴展和理解的軟體。
SOLID 原則是下面這幾個原則的縮寫,每個原則讓我們用輕鬆的方式來說明:
單一職責原則(Single Responsibility Principle - SRP):
就像小時候被教育應該專注負責做一件事一樣,一個物件或類別也應該只做一件事情。
比如,如果你是負責畫圖的小畫家,你就專注畫圖就好,不要再去唱歌跳舞或是打遊戲。
開放/封閉原則(Open/Closed Principle - OCP):
就像玩樂高積木,可以一直添加新的積木,但不需要改變已經建好的部分。
你可以建立另外再加蓋新的城堡或汽車,而不需要破壞以前建好的東西。
里氏替換原則(Liskov Substitution Principle - LSP):
如果有一個玩具火車,可以用不同的顏色或形狀的火車車廂來替換,但它們都應該能夠在軌道上運行,不應該有問題。這就像在使用不同的玩具部件時,不應該出現問題。
介面隔離原則(Interface Segregation Principle - ISP):
這就像是玩具箱,可以選擇玩具,不需要使用所有的玩具。
例如,你可以選擇用樂高積木來建城堡,而不使用汽車玩具。每個玩具都有自己的用途,不需要用到所有的。
依賴反轉原則(Dependency Inversion Principle - DIP):
這就像想要玩遙控車,可以用一個遙控器來控制玩具,而不是直接抓車子本身去移動。
遙控器是一個抽象的方式來控制不同的玩具,你可以改變遙控器上面的搖桿的方向,而不是直接移動車子本身。這樣更容易控制你的玩具,而不需要改變它們。
這些 SOLID 原則是物件導向軟體設計的基石,幫助開發人員建立高質量、可維護的程式碼。遵循這些原則有助於提高程式的可讀性、可擴展性和可維護性,並降低變更代碼時引入錯誤的風險。
下面使用Day 9 有提到的Method,來舉一些不遵循單一職責原則的例子,以及如何將它們拆分成單一職責的方法:
合併並排序列表: 假設有一個Method需要合併兩個不同的列表並對它們進行排序。
這個Method實際上執行了兩個不同的任務:合併和排序。
解決方法是將這兩個功能分別封裝到兩個不同的Method中,一個用於合併,另一個用於排序。這樣做的好處是提高代碼的可讀性、可維護性和可測試性。
// 不遵循單一職責原則
public List<int> MergeAndSortLists(List<int> list1, List<int> list2)
{
var mergedList = list1.Concat(list2).ToList();
return mergedList.OrderBy(x => x).ToList();
}
在這個Method中,它執行了兩個不同的任務:
list1
和 list2
合併成一個新的列表 mergedList
。mergedList
列表進行排序。// 遵循單一職責原則
public List<int> MergeLists(List<int> list1, List<int> list2)
{
return list1.Concat(list2).ToList();
}
public List<int> SortList(List<int> list)
{
return list.OrderBy(x => x).ToList();
}
在這個版本中,可以看到把合併和排序拆分成了兩個不同的Method:
MergeLists
Method接受兩個列表 list1
和 list2
並返回它們的合併結果。SortList
Method接受一個列表 list
並返回排序後的結果。這樣的好處是,現在每個Method只負責一個特定的任務,更容易理解和測試。
如果以後需要修改合併邏輯或排序邏輯,只需要關注相應的Method,而不會影響到其他部分的代碼,而且這種方式也有助於提高代碼的可重用性,因為你也可以在其他地方使用這些函數來執行相同的操作。
再來舉其他例子看看。
檢查並儲存檔案: 假設有一個Method需要檢查檔案是否存在,需要執行一個文件處理相關的操作,其中包括檢查文件是否存在並保存文件內容。
不遵循單一職責原則的版本,可以看到Method執行了兩個不同的操作。
// 不遵循單一職責原則
public void CheckAndSaveFile(string filePath, string content)
{
if (File.Exists(filePath))
{
// 儲存檔案
File.WriteAllText(filePath, content);
}
}
在這個Method中,它執行了兩個不同的任務:
File.Exists(filePath)
檢查指定路徑的文件是否存在。File.WriteAllText(filePath, content)
來儲存文件內容。// 遵循單一職責原則
public bool FileExists(string filePath)
{
return File.Exists(filePath);
}
public void SaveFile(string filePath, string content)
{
File.WriteAllText(filePath, content);
}
在這個版本中,我們將檢查文件是否存在和保存文件內容分為兩個不同的Method:
FileExists
Method接受文件路徑 filePath
並返回一個布林值,表示該文件是否存在。SaveFile
Method接受文件路徑 filePath
和文件內容 content
,並將內容儲存到指定的文件中。身為.NET工程師,多數時間都需要與資料庫操作(把資料查找出來,新增修改或刪除)。
資料庫操作和業務邏輯混合: 在下面的例子裏面,涉及到更新使用者信息的操作,同時伴隨著業務邏輯,其中包括年齡檢查和通知。我們可以將這些操作拆分成兩個遵循單一職責原則的Method。
首先如果不遵循單一職責原則
// 不遵循單一職責原則
public void UpdateUserInformation(User user)
{
// 資料庫更新操作
database.UpdateUser(user);
// 業務邏輯
if (user.Age < 18)
{
SendNotification(user);
}
}
在這個Method中,它同時執行了兩個不同的任務:
database.UpdateUser(user)
來更新使用者的資料庫信息。SendNotification(user)
來發送通知。那如果是改成遵循單一職責原則的版本的話
// 遵循單一職責原則
public void UpdateUserInformation(User user)
{
// 資料庫更新操作
database.UpdateUser(user);
}
public void NotifyIfUnderage(User user)
{
// 業務邏輯
if (user.Age < 18)
{
SendNotification(user);
}
}
將資料庫更新操作和業務邏輯拆分為兩個不同的方法:
UpdateUserInformation
這個Method負責執行資料庫的更新操作,並傳入使用者信息。NotifyIfUnderage
這個Method負責執行業務邏輯,檢查使用者的年齡是否小於 18 歲,如果是,則調用 SendNotification(user)
來發送通知。各司其職,這樣分開的好處是如果以後需要修改資料庫更新邏輯或業務邏輯,只需要關注相應的Method,而不會影響到其他部分的程式碼。
第12天挑戰完成!!!