什麼是 Single Responsibility Principle(SRP)?

 

為什麼好的程式一定要遵守它?

在軟體開發中,我們常聽到 SOLID 原則,而其中最容易理解、卻也最容易被忽略的,就是 Single Responsibility Principle(單一職責原則,簡稱 SRP)

許多專案在初期「看起來沒問題」,但隨著需求變多、維護人員更替,程式碼卻開始變得難以修改、容易出錯,這通常就是 違反 SRP 的典型後果


一、什麼是 Single Responsibility Principle?

Single Responsibility Principle 的定義是:

一個類別(Class)應該只負責一件事情,並且只有一個改變的理由。

換句話說:

  • 一個類別

  • 一個職責

  • 一種變更原因

如果一個類別因為「不同原因」而必須修改,那就代表它承擔了不只一個責任


二、為什麼開發要使用 SRP?

在實務上,違反 SRP 常常會造成以下問題:

❌ 常見問題

  • 修改一個功能,卻影響其他不相關邏輯

  • 類別過大,閱讀困難

  • 測試困難(必須一次測一堆功能)

  • 新人接手不敢動程式碼

  • 需求一多就開始複製貼上

✅ SRP 帶來的好處


優勢 說明
可讀性高 每個類別只負責單一職責,程式碼意圖清楚,降低理解成本
維護性佳 修改某一功能時,不會影響其他不相關的邏輯
易於測試 單元測試可以只針對單一行為,不需要初始化複雜環境
降低耦合 各職責彼此獨立,減少類別之間的相依關係
易於重構 當需求變動時,可局部調整或替換實作,風險較低

👉 對你這種常需要維護舊系統、重構、寫單元測試的工程師來說,SRP 幾乎是基本功。


三、什麼情境「特別需要」使用 SRP?

以下情境強烈建議使用 SRP

  • 商業邏輯 + 資料存取 混在一起

  • 一個類別同時處理:

    • 驗證

    • 計算

    • 寫資料庫

    • 寫 Log

    • 發 Email

  • 需求常變動(報表、流程、規則)

  • 專案需要多人維護

  • 需要寫單元測試(MSTest / xUnit)


四、沒有使用 SRP 的程式會長怎樣?

❌ 範例:違反 SRP 的寫法

public class OrderService { public void CreateOrder(Order order) { // 1. 驗證訂單 if (order.Amount <= 0) { throw new Exception("金額錯誤"); } // 2. 計算折扣 if (order.Amount > 1000) { order.Amount *= 0.9m; } // 3. 寫入資料庫 using (var conn = new SqlConnection("...")) { conn.Open(); // Insert Order } // 4. 寫 Log File.AppendAllText("log.txt", "Order Created"); // 5. 發送 Email SendEmail(order); } private void SendEmail(Order order) { // 發送通知信 } }

❌ 問題分析

這個 OrderService 同時負責:

  1. 驗證規則

  2. 商業邏輯(折扣)

  3. 資料存取

  4. 記錄 Log

  5. 通知 Email

👉 任何一個需求變更,都可能要修改這個類別


五、使用 SRP 重構後會長怎樣?

✅ 重構後的設計

1️⃣ 驗證職責

public class OrderValidator { public void Validate(Order order) { if (order.Amount <= 0) { throw new Exception("金額錯誤"); } } }

2️⃣ 商業邏輯(折扣)

public class OrderCalculator { public void ApplyDiscount(Order order) { if (order.Amount > 1000) { order.Amount *= 0.9m; } } }

3️⃣ 資料存取

public class OrderRepository { public void Save(Order order) { using (var conn = new SqlConnection("...")) { conn.Open(); // Insert Order } } }

4️⃣ 通知服務

public class EmailService { public void SendOrderCreatedMail(Order order) { // 發送 Email } }

5️⃣ 協調流程(Orchestrator)

public class OrderService { private readonly OrderValidator _validator; private readonly OrderCalculator _calculator; private readonly OrderRepository _repository; private readonly EmailService _emailService; public OrderService() { _validator = new OrderValidator(); _calculator = new OrderCalculator(); _repository = new OrderRepository(); _emailService = new EmailService(); } public void CreateOrder(Order order) { _validator.Validate(order); _calculator.ApplyDiscount(order); _repository.Save(order); _emailService.SendOrderCreatedMail(order); } }

六、這樣做的實際好處是什麼?

✔ 修改風險大幅降低

  • 改折扣 → 不影響資料庫

  • 改 Email → 不動訂單邏輯

✔ 更容易寫單元測試

[TestMethod] public void ApplyDiscount_ShouldApply_WhenAmountOver1000() { var order = new Order { Amount = 2000 }; var calculator = new OrderCalculator(); calculator.ApplyDiscount(order); Assert.AreEqual(1800, order.Amount); }

👉 不需要資料庫、不需要 Email、不需要 Mock 一堆東西


七、總結:如何判斷你的程式是否違反 SRP?

你可以問自己三個問題:

  1. 這個類別在做幾件事?

  2. 如果需求改變,會有幾種理由要修改它?

  3. 單元測試是不是很難寫?

只要答案不是,就很可能該重構了。

留言

熱門文章