設計原則 - 單一職責原則 Single Responsibility Principle

在寫程式的過程中,我們經常會遇到一個類別或模組負責太多事情,結果導致程式碼難以維護或修改,這種情況常常被稱為「巨石類別 (God Class)」。當我們需要對程式碼做出任何變更時,這種「巨石類別」會變得相當脆弱,因為它的每一個改動都有可能對其他功能造成影響。這時候,「單一職責原則 (Single Responsibility Principle, SRP)」便能幫助我們解決這個問題。

什麼是單一職責原則?

簡單來說,單一職責原則強調每個類別或模組應該只負責一件事,也就是說每個類別應該只專注於它的核心功能。如果一個類別有多於一個原因會導致它需要變更,那麼這個類別就違反了單一職責原則。

用白話來說,就是「一個人做好一件事」的概念。在軟體設計中,我們應該避免讓一個類別承擔太多不同的責任,這樣不僅會讓程式碼更具彈性,也更容易進行維護。

單一職責原則的例子

讓我們來看一個簡單的例子,理解什麼樣的設計違反了單一職責原則。

錯誤的設計,違反單一職責原則

我們可以先從一個不遵守單一職責原則的範例來看。假設你有一個「報告管理」類別,這個類別同時負責產生報告內容和報告的儲存:

1
2
3
4
5
6
7
8
9
10
class ReportManager {
public:
void generateReport() {
// 產生報告的邏輯
}

void saveToFile(const std::string& filename) {
// 將報告儲存至檔案
}
};

這個 ReportManager 類別明顯負責了兩件事:一是產生報告,二是將報告儲存至檔案。這樣的設計違反了單一職責原則,因為當你需要修改報告產生邏輯時,可能會影響到報告儲存的邏輯,反之亦然。

這種違反單一職責原則的設計會使程式碼變得難以維護。隨著功能增多,我們可能會發現 ReportManager 會變得越來越臃腫,最終變成「巨石類別」,任何改動都會影響整個類別的穩定性。

正確的設計,遵守單一職責原則

那麼要如何設計一個符合單一職責原則的解決方案呢?我們可以將 ReportManager 的責任拆分成兩個類別,一個專注於報告產生,另一個專注於報告儲存:

1
2
3
4
5
6
7
8
9
10
11
12
13
class ReportGenerator {
public:
void generateReport() {
// 產生報告的邏輯
}
};

class ReportSaver {
public:
void saveToFile(const std::string& filename) {
// 將報告儲存至檔案的邏輯
}
};

現在我們的 ReportGenerator 類別只專注於報告產生,而 ReportSaver 類別則負責儲存報告。這樣的設計遵守了單一職責原則,因為每個類別只負責一件事。當我們需要修改報告產生的邏輯時,並不會影響到報告儲存的功能,反之亦然。

為什麼這麼做?

單一職責原則的最大好處在於降低類別之間的耦合度。當每個類別都只負責一件事時,程式碼變更時不會影響到其他不相關的部分,這樣能夠有效降低系統的出錯機率。這也讓我們的程式碼更具可測試性,因為每個類別都只有單一的責任,單元測試時只需要關注這個類別的核心功能即可。

此外,單一職責原則還提升了系統的可維護性。隨著需求的變更,我們能夠更加輕鬆地對系統進行修改,因為我們不必擔心一個類別承擔了太多的責任,導致改動影響整個系統的穩定性。

當然,過度拆分職責可能會導致過度設計,特別是在小型專案中,每個功能都被分得太細反而會增加程式碼的複雜性。因此遵守單一職責原則時,需要根據實際需求找到平衡。

實際應用

單一職責原則在實際開發中應用廣泛,尤其是在大型專案或持續演進的系統中尤為重要。無論是類別設計還是模組拆分,遵守單一職責原則能夠有效提升系統的可擴展性與可維護性。

例如,在設計軟體框架時,我們會將不同功能的模組分開,確保每個模組只負責單一職責,這樣開發者在擴展功能時,可以很輕鬆地新增或修改特定模組,而不必擔心破壞其他模組的運作。