首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

C# 設計模式核心原則:單一職責原則 (SRP) 深度解析與實戰重構

     編輯:ASP.NET 基礎

核心定義

單一職責原則 (Single Responsibility Principle, SRP):
一個類別應該**只有一個引起它變更的原因**,即**只做一件事**。
— Robert C. Martin (Uncle Bob)

SRP 是 SOLID 五大原則之首,是**低耦合、高內聚**在類別層級的具體體現。職責過多會導致:修改一處牽一髮動全身、測試困難、團隊協作衝突。

案例一:使用者介面拆分 (介面隔離)

違反 SRP 的設計

違反 SRP:單一介面包含屬性與行為

// 問題:IUser 同時包含資料屬性 (BO) 與業務行為 (Biz)
// 任何屬性變更或行為變更都會影響實作類別
public interface IUser
{
    string Name { get; set; }
    int Age { get; set; }
    void Add();
    void Update();
    void Delete();
}

重構:拆分為兩個介面

拆分為 IUserBO 與 IUserBiz

// 資料傳輸介面 (BO - Business Object)
public interface IUserBO
{
    string Name { get; set; }
    int Age { get; set; }
}

// 業務行為介面
public interface IUserBiz
{
    void Add(IUserBO user);
    void Update(IUserBO user);
    void Delete(string userId);
}

// 實作類別可同時實作兩介面,視使用情境注入不同介面
public class UserInfo : IUserBO, IUserBiz { /* ... */ }

// 使用端:依介面程式設計
public class UserService
{
    private readonly IUserBiz _biz;
    public UserService(IUserBiz biz) => _biz = biz;
    public void Register(IUserBO user) => _biz.Add(user);
}

效益:資料結構變更 (如新增 Email) 僅影響 `IUserBO` 實作;業務流程變更 (如加入驗證) 僅影響 `IUserBiz` 實作,兩者**獨立演進**。

案例二:矩形類別職責過重

違反 SRP

Rectangle 同時負責繪圖與面積計算

public class Rectangle
{
    public int Width { get; set; }
    public int Height { get; set; }

    public void Draw(Graphics g) { /* 繪圖邏輯,依賴 UI 庫 */ }
    public int Area() => Width * Height; // 純計算,無 UI 相依
}

問題:

  • 繪圖邏輯變更 (如換 UI 框架) → 需重新編譯/部署整個類別,連帶影響只需計算面積的模組。
  • 單元測試 `Area()` 卻需引用 `System.Drawing` 等 UI 組件。

重構:職責分離

分離為 RectangleGeometry 與 RectangleRenderer

// 幾何計算 - 穩定、無外部相依
public class RectangleGeometry
{
    public int Width { get; set; }
    public int Height { get; set; }
    public int Area() => Width * Height;
}

// 繪圖渲染 - 易變、依賴 UI
public class RectangleRenderer
{
    public void Draw(Graphics g, RectangleGeometry geom) { /* ... */ }
}

案例三:電話介面雙重職責

違反 SRP

public interface IPhone
{
    void Dial(string number);      // 協議管理:建立連線
    void HangUp();                 // 協議管理:斷開連線
    void Chat(object data);        // 資料傳輸:通話內容
    void Answer(object data);      // 資料傳輸:接收內容
}

兩大職責:通訊協議管理 (Dial/HangUp) 與 資料傳輸 (Chat/Answer)。協議變更 (如 5G) 與資料格式變更 (如加密) 獨立演進。

重構方案比較

方案 A:介面拆分 + 組合 (強耦合)

介面拆分+組合

public interface IProtocol { void Dial(string n); void HangUp(); }
public interface IDataTransfer { void Chat(object d); void Answer(object d); }

// 組合關係:Phone 包含 IProtocol 與 IDataTransfer 實例
public class Phone
{
    public IProtocol Protocol { get; set; }
    public IDataTransfer Transfer { get; set; }
}
// 缺點:組合為強耦合,生命週期綁定,擴充性受限

方案 B:單一類別實作多介面 (推薦,鬆耦合)

單一類別實作多介面

public class SmartPhone : IProtocol, IDataTransfer
{
    public void Dial(string n) { /* 5G/4G 邏輯 */ }
    public void HangUp() { /* ... */ }
    public void Chat(object d) { /* 加密傳輸 */ }
    public void Answer(object d) { /* 解密接收 */ }
}

// 使用端:依介面注入,互不干擾
public class CallManager
{
    private readonly IProtocol _protocol;
    public CallManager(IProtocol protocol) => _protocol = protocol;
    public void StartCall(string num) => _protocol.Dial(num);
}

優勢:

  • 對外公開**介面**而非具體類別,呼叫端僅依賴所需職責介面。
  • 實作類別內部雖有兩個變更原因,但**外部模組各自只依賴單一介面**,符合「介面隔離原則 (ISP)」並間接滿足 SRP。
  • 無需額外組合物件,生命週期自管,簡單直接。

SRP 實戰檢核清單

檢核點違反徵兆重構手法
類別行數 > 200方法過多、邏輯雜糅Extract Class / Extract Interface
引用命名空間 > 5跨層相依 (UI、DB、Network 混雜)分層架構、依賴倒置
修改原因 > 1改 UI 牽動 DB、改演算法牽動介面拆分類別、Facade/Proxy 模式
單元測試需大量 Mock相依過多外部服務介面隔離、注入 Mock

SRP 帶來的具體效益

  • 可讀性提升:類別職責單一,命名清晰,新人易上手。
  • 可維護性提升:修改範圍局限,降低回歸風險。
  • 可測試性提升:單一職責易構建測試夾具,Mock 依賴少。
  • 可復用性提升:純計算/資料類別可跨專案共用,無 UI/DB 綁定。
  • 團隊協作順暢:不同職責由不同人維護,減少合併衝突。

結語

SRP 不是「類別越小越好」,而是**依業務變更軸線劃分邊界**。識別「什麼會一起變、什麼會分開變」是架構師的核心能力。實務中結合 **ISP (介面隔離)**、**DIP (依賴倒置)**、 **Facade/Proxy/Strategy** 等模式,才能落地「高內聚、低耦合」的系統架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved