Uncle Bob: 「A class should have only one reason to change.」
一個類別應該只有一個改變的理由,也就是一個類別應該只有一個職責。這有助於保持程式碼的可讀性、可維護性和重用性。
簡單來說,一個類別負擔太多職責時,代表該類別可以被切割,切割之後,每個 Class 負擔的職責就變少了,也就提高了內聚力。
實務常見問題
- 所有功能寫在一個類別中
- 類別中的方法太多,不知道要呼叫哪個方法
最佳實務
- 把大類別切割成多個小類別
- 對類別進行適度的切割可以提高可讀性、可維護性和重用性
當你有不同的責任產生變更需求而又會改到同個類別時,可能就需要切割類別。
用 C# 舉例來說,假設我們有一個 Report
類別負責生成報告和將報告保存到文件:
using System;
using System.IO;
// 不符合單一職責原則的類別
public class Report
{
public void GenerateReport()
{
// 生成報告的邏輯
Console.WriteLine("Generating report...");
Console.WriteLine($"Title: ...");
Console.WriteLine($"Content: ...");
}
public void SaveToFile(string fileName)
{
// 將報告保存到文件的邏輯
Console.WriteLine($"Saving report to ...");
}
}
public class Main()
{
var reportGenerator = new ReportGenerator();
neportGenerator.GenerateReport();
neportGenerator.SaveToFile();
}
上面的 Report
類別同時負責生成報告和保存報告到文件,有幾個會改類別的理由?
- 生成報告的格式改變
- 儲存的路徑改變
超過一個 reason to change,這違反了單一職責原則。現在,我們應該將這兩個職責分開,分別由不同的類別來處理。
// 符合單一職責原則的類別
public class ReportGenerator
{
public void GenerateReport()
{
// 生成報告的邏輯
Console.WriteLine("Generating report...");
Console.WriteLine($"Title: ...");
Console.WriteLine($"Content: ...");
}
}
public class ReportSaver
{
public void SaveToFile()
{
// 將報告保存到文件的邏輯
Console.WriteLine($"Saving report to ...");
}
}
public class Main()
{
//耦合力變高了,相依於 2 個類別
new ReportGenerator().GenerateReport();
new ReportSaver().SaveToFile();
}
現在,Report
類別只負責存儲報告的數據,而 ReportGenerator
和 ReportSaver
分別負責生成報告和保存報告到文件的相應職責。這樣使得程式碼更容易理解、維護和擴展。當報告生成邏輯或保存邏輯發生變化時,只需要修改相應的類別,而不會影響到其他部分的程式碼。
一個類別中的一段程式碼有重複利用的需求,在其他類別也用的到,這個時候就該切割。
SRP使用注意事項
- 一個類別被賦予過多責任,因此切割類別出去,提高了內聚力,但同時也提高了耦合力,類別數量也變多,所以除了 SRP 還要再考慮其他原則才能真正實現高內聚低耦合
- 如果沒有出現需求,不需要第一時間就分離責任