C#_接口設計:角色與契約的分離


2.3 接口設計:角色與契約的分離

在軟件架構中,接口(Interface)遠不止是一種語言結構。它是一份契約(Contract),明確規定了實現者必須提供的能力,以及使用者可以依賴的服務。優秀的接口設計是構建松散耦合、易于測試和長期可維護系統的基石。

2.3.1 契約的本質:承諾與期望

一個接口定義了一個角色(Role)所能執行的操作。任何實現了該接口的類,就是在承諾它能夠扮演這個角色,履行契約規定的所有義務。

  • 對實現者的要求:“你必須提供這些方法,并遵守其隱含的行為規范(如:GetUserById 在找不到時應返回null還是拋出異常?)。”
  • 對使用者的承諾:“你可以放心地調用這些方法,它們會按照文檔描述的方式工作,你無需關心背后的實現細節。”

這種將“契約”與“實現”分離的能力,是依賴倒置原則(DIP)得以實現的技術基礎。

2.3.2 設計原則:精煉、專注與穩定

  1. 小而專(遵循ISP):我們在2.1節已經接觸了接口隔離原則(ISP)。接口應該盡可能地小和專注,只包含一組高度相關的方法。一個接口只定義一個角色,而不是多個角色的混合。

    反面教材(胖接口):

    public interface IDataService { // 承擔了太多角色// CRUD角色void CreateEntity(Entity e);Entity ReadEntity(int id);void UpdateEntity(Entity e);void DeleteEntity(int id);// 報表角色Report GenerateMonthlyReport();DataSet GetHistoricalData(DateTime start, DateTime end);// 工具角色bool ValidateEntity(Entity e);string ExportToCsv();
    }
    

    重構方案(角色分離):

    public interface IEntityRepository { // 職責:實體持久化void Create(Entity e);Entity Read(int id);void Update(Entity e);void Delete(int id);
    }public interface IReportGenerator { // 職責:生成報表Report GenerateMonthlyReport();DataSet GetHistoricalData(DateTime start, DateTime end);
    }public interface IEntityValidator { // 職責:驗證實體bool Validate(Entity e);
    }public interface IDataExporter { // 職責:數據導出string ExportToCsv();
    }
    

    現在,一個類可以根據需要實現一個或多個這些細粒度的接口,客戶端也只需依賴它們真正需要的接口。

  2. 命名揭示意圖:接口的名稱應該清晰地表明其角色和契約的本質。

    • 使用名詞:用于表示“是什么”,通常代表一個服務(如 IRepository, INotifier)。
    • 使用形容詞:用于表示“有什么能力”,通常用于修飾實體(如 IDisposable, IComparable)。-able 后綴是一個常見的約定。
    • 避免“I”前綴之外的冗余IUserService 就比 IUserServiceInterface 好。
  3. 面向抽象,而非實現:在定義接口時,要思考“使用者需要什么”,而不是“實現者會怎么做”。接口方法應該接收和返回抽象類型(接口、抽象類)而不是具體實現類,這樣才能最大限度地減少耦合。

    不佳的設計:

    public interface IOrderProcessor {// 依賴具體類 SqlServerOrderRepository,將實現細節泄露給了接口契約void ProcessOrder(Order order, SqlServerOrderRepository repository);
    }
    

    良好的設計:

    public interface IOrderProcessor {// 依賴抽象 IOrderRepository,任何實現該接口的倉庫都可以被接受void ProcessOrder(Order order, IOrderRepository repository);
    }
    
  4. 版本化與破壞性變更:接口一旦被公開并有多方實現和使用,就應視為一種穩定的公共API。向接口添加新成員是一個破壞性變更,會導致所有現有的實現者無法編譯。在設計初期,通過ISP創建小接口可以減少此類問題的發生。如果后期必須添加功能,有幾種策略:

    • 創建新接口IAdvancedReportGenerator : IReportGenerator
    • 使用默認接口方法(C# 8.0+):允許在接口中提供方法的默認實現,從而在不破壞現有實現的情況下添加功能。
      public interface IReportGenerator {Report GenerateMonthlyReport();// 新方法,提供了默認實現,舊的實現類不需要修改DataSet GetHistoricalData(DateTime start, DateTime end) => throw new NotImplementedException("This implementation does not support historical data.");
      }
      
    • 謹慎使用默認接口方法:它雖然解決了兼容性問題,但也可能使接口變得臃腫,模糊了接口作為“純粹契約”的界限。最好用于真正有向前兼容需求的場景,而不是作為設計初期偷懶的工具。

2.3.3 實戰:為緩存設計接口

讓我們通過一個例子來實踐上述原則。我們需要為一個緩存服務設計接口。

初版設計:

public interface ICache {void Set(string key, object value);object Get(string key);void Remove(string key);void Clear();bool Contains(string key);
}

這個接口很簡單,但它有一些問題:

  1. 沒有過期時間的概念。
  2. Get 方法返回 object,使用者需要強制類型轉換,既不安全也不方便。
  3. 它是同步的,可能無法滿足異步緩存客戶端(如Redis)的需求。

改進版設計(應用設計原則):

// 一個更精煉、更健壯、更易用的緩存接口契約
public interface ICache {// 基礎操作Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default);Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);Task RemoveAsync(string key, CancellationToken cancellationToken = default);Task<bool> ContainsAsync(string key, CancellationToken cancellationToken = default);// 可選:提供同步版本的方法(如果確實需要,但優先異步)void Set<T>(string key, T value, TimeSpan? expiration = null);T? Get<T>(string key);// ... 其他同步方法
}// 甚至,我們可以根據ISP進一步拆分,比如將分布式緩存特有的功能(如原子遞增)分離出去
public interface IDistributedCache : ICache {Task<long> IncrementAsync(string key, long value = 1, CancellationToken cancellationToken = default);
}

改進點分析:

  1. 異步優先:方法命名為 ...Async 并返回 Task,支持異步操作和取消請求。
  2. 泛型方法GetAsync<T>SetAsync<T> 提供了類型安全,使用者無需強制轉換。
  3. 可選參數expiration 參數提供了靈活性,同時保持了簡潔性。
  4. 明確的命名:方法名清晰地揭示了其意圖。
  5. 擴展性:通過 IDistributedCache 繼承 ICache,為更高級的緩存需求提供了擴展點,而沒有污染基礎的緩存契約。

2.3.4 架構師視角:接口是系統設計的核心工具

作為架構師,你在接口設計中的角色是:

  • 定義系統邊界:通過接口明確模塊之間的交互契約,從而實現關注點分離和高內聚、低耦合。
  • ** enabling Testability**:定義清晰的接口是實現高效單元測試的關鍵,因為它允許輕松地用Mock或Stub替換真實實現。
  • 指導而非限制:好的接口為實現者提供了明確的指導,同時又給予了他們選擇如何實現契約的自由度。
  • 演化式設計:承認你無法一開始就設計出完美的接口。接口應該隨著對領域理解的深入而演化。運用ISP,你可以輕松地通過拆分和重組接口來適應變化,而不是修改一個龐大的、僵化的契約。

總結:
接口是軟件架構中最重要的抽象工具之一。設計良好的接口——精煉、專注、穩定且意圖明確——是構建能夠經受住時間考驗的靈活系統的關鍵。它不僅僅是一種語法,更是一種設計哲學,體現了對角色、契約和職責分離的深刻思考。始終從使用者的角度出發,定義你希望提供的服務,而不是你打算如何實現它,這將引領你走向更清晰、更穩健的架構設計。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/96489.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/96489.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/96489.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

vsCode或Cursor 使用remote-ssh插件鏈接遠程終端

一、Remote-SSH介紹Remote-SSH 是 VS Code 官方提供的一個擴展插件&#xff0c;允許開發者通過 SSH 協議連接到遠程服務器&#xff0c;并在本地編輯器中直接操作遠程文件&#xff0c;實現遠程開發。它將本地編輯器的功能&#xff08;如語法高亮、智能提示、調試等&#xff09;與…

C語言實戰:從零開始編寫一個通用配置文件解析器

資料合集下載鏈接: ?https://pan.quark.cn/s/472bbdfcd014? 在軟件開發中,我們經常需要將一些可變的參數(如數據庫地址、端口號、游戲角色屬性等)與代碼本身分離,方便日后修改而無需重新編譯整個程序。這種存儲配置信息的文件,我們稱之為配置文件。 一、 什么是配置…

車機兩分屏運行Unity制作的效果

目錄 效果概述 實現原理 完整實現代碼 實際車機集成注意事項 1. 顯示系統集成 多屏顯示API調用 代碼示例&#xff08;AAOS副駕屏顯示&#xff09; 2. 性能優化 GPU Instancing 其他優化技術 3. 輸入處理 觸控處理 物理按鍵處理 4. 安全規范 駕駛員側限制 乘客側…

vivo“空間計算-機器人”生態落下關鍵一子

出品 | 何璽排版 | 葉媛不出所料&#xff0c;vivo Vision熱度很高。從21號下午發布到今天&#xff08;22號&#xff09;&#xff0c;大眾圍繞vivo Vision探索版展開了多方面的討論&#xff0c;十分熱烈。從討論來看&#xff0c;大家現在的共識是&#xff0c;MR行業目前還處于起…

Azure TTS Importer:一鍵導入,將微軟TTS語音接入你的閱讀軟件!

Azure TTS Importer&#xff1a;一鍵導入&#xff0c;將微軟TTS語音接入你的閱讀軟件&#xff01; 文章來源&#xff1a;Poixe AI 厭倦了機械、生硬的文本朗讀&#xff1f;想讓你的閱讀軟件擁有自然流暢的AI語音&#xff1f;今天&#xff0c;我們將為您介紹一款強大且安全的開…

用過redis哪些數據類型?Redis String 類型的底層實現是什么?

Redis 數據類型有哪些&#xff1f; 詳細可以查看&#xff1a;數據類型及其應用場景 基本數據類型&#xff1a; String&#xff1a;最常用的一種數據類型&#xff0c;String類型的值可以是字符串、數字或者二進制&#xff0c;但值最大不能超過512MB。一般用于 緩存和計數器 Ha…

大視協作碼垛機:顛覆傳統制造,開啟智能工廠新紀元

在東三省某食品廠的深夜生產線上&#xff0c;碼垛作業正有序進行&#xff0c;卻不見人影——這不是魔法&#xff0c;而是大視協作碼垛機器人帶來的現實變革。在工業4.0浪潮席卷全球的今天&#xff0c;智能制造已成為企業生存與發展的必由之路。智能碼垛環節作為產線的關鍵步驟&…

c# 保姆級分析繼承詳見問題 父類有一個列表對象,子類繼承這個列表對象并對其進行修改后,將子類對象賦值給父類對象,父類對象是否能包含子類新增的內容?

文章目錄 深入解析:父類與子類列表繼承關系的終極指南 一、問題背景:從實際開發困惑說起 二、基礎知識回顧:必備概念理解 2.1 繼承的本質 2.2 引用類型 vs 值類型 2.3 多態的實現方式 三、核心問題分析:列表繼承場景 3.1 基礎代碼示例 3.2 關鍵問題分解 3.3 結論驗證 四、深…

tensorflow-gpu 2.7下的tensorboard與profiler插件版本問題

可行版本&#xff1a; python3.9.23cuda12.0tensorflow-gpu2.7.0tensorboard2.20.0 tensorboard-plugin-profile 2.4.0 問題描述&#xff1a; 1. 安裝tensorboard后運行tensorboard --logdirlogs在網頁中打開&#xff0c;發現profile模塊無法顯示&#xff0c;報錯如下&#x…

數據結構青銅到王者第一話---數據結構基本常識(1)

目錄 一、集合框架 1、什么是集合框架 2、集合框架的重要性 2.1開發中的使用 2.2筆試及面試題 3、背后涉及的數據結構以及算法 3.1什么是數據結構 3.2容器背后對應的數據結構 3.3相關java知識 3.4什么是算法 3.5如何學好數據結構以及算法 二、時間和空間復雜度 1、…

【Verilog】延時和時序檢查

Verilog中延時和時序檢查1. 延時模型1.1 分布延遲1.2 集總延遲1.3 路徑延遲2. specify 語法2.1 指定路徑延時基本路徑延時邊沿敏感路徑延時狀態依賴路徑延時2.2 時序檢查$setup, $hold, $setuphold$recovery, $removal, $recrem$width, $periodnotifier1. 延時模型 真實的邏輯元…

DigitalOcean Gradient AI平臺現已支持OpenAI gpt-oss

OpenAI 的首批開源 GPT 模型&#xff08;200 億和 1200 億參數&#xff09;現已登陸 Gradient AI 平臺。此次發布讓開發者在構建 AI 應用時擁有更高的靈活度和更多選擇&#xff0c;無論是快速原型還是大規模生產級智能體&#xff0c;都能輕松上手。新特性開源 GPT 模型&#xf…

藏在 K8s 幕后的記憶中樞(etcd)

目錄1&#xff09;etcd 基本架構2&#xff09;etcd 的讀寫流程總覽a&#xff09;一個讀流程b&#xff09;一個寫流程3&#xff09;k8s存儲數據過程源碼解讀4&#xff09;watch 機制Informer 機制etcd watch機制etcd的watchableStore源碼解讀5&#xff09; k8s大規模集群時會存在…

騰訊云EdgeOne安全防護:快速上手,全面抵御Web攻擊

為什么需要專業的安全防護&#xff1f; 在當今數字化時代&#xff0c;網站面臨的安全威脅日益增多。據統計&#xff0c;2023年全球Web應用程序攻擊超7千億次&#xff0c;持續快速增長。 其中最常見的包括&#xff1a; DDoS攻擊&#xff1a;通過海量請求使服務器癱瘓Web應用攻…

SpringBoot中的條件注解

文章目錄前言什么是條件注解核心原理常用條件注解詳解1. ConditionalOnClass和ConditionalOnMissingClass2. ConditionalOnBean和ConditionalOnMissingBean3. ConditionalOnProperty應用場景&#xff1a;多數據源配置在SpringBoot自動配置中的核心作用自動配置的工作原理經典自…

LightGBM時序預測詳解:從原理到 PSO 參數優化

前言 在時間序列預測領域&#xff0c;集成學習方法一直占據重要地位。此前我們介紹了基于傳統集成思想的時序預測方法&#xff08;查看前文&#xff09;&#xff0c;而梯度提升樹&#xff08;GBDT&#xff09;作為集成學習的佼佼者&#xff0c;在時序預測中表現尤為突出。本文…

django生成遷移文件,執行生成到數據庫

當報錯時 重新拉取git&#xff0c;重新生成遷移文件&#xff0c;重新執行 1、生成遷移文件 python manage.py makemigrations 子應用2、執行建表、建字段、修改字段 python manage.py migrate 子應用3、當手動已經在數據庫創建字段時&#xff0c; 用 --fake 標記遷移為 “已應用…

2025軟件供應鏈安全技術路線未來趨勢預測

軟件供應鏈安全已從一個技術圈的議題演變為全球企業的治理焦點。近幾年&#xff0c;APT滲透、惡意包植入、開發者誤操作等不同類型的供應鏈安全事件頻發&#xff0c;使得“安全的代碼來源”和“可信的交付鏈路”成為企業數字化轉型的生命線。2025年的軟件供應鏈安全&#xff0c…

用戶登錄Token緩存Redis實踐:提升SpringBoot應用性能

前言在現代Web應用中&#xff0c;用戶認證和授權是至關重要的功能。傳統的基于數據庫的Token存儲方式雖然簡單易用&#xff0c;但在高并發場景下容易成為性能瓶頸。本文將介紹如何將SpringBoot項目中的用戶Token從數據庫存儲遷移到Redis緩存&#xff0c;顯著提升系統性能。一、…

深度解析Structured Outputs:讓AI輸出嚴格遵循JSON Schema的結構化響應

深度解析Structured Outputs&#xff1a;讓AI輸出嚴格遵循JSON Schema的結構化響應 引言 在現代應用開發中&#xff0c;JSON 是最流行的數據交換格式之一。為了提升 API 接口的健壯性和數據一致性&#xff0c;結構化輸出&#xff08;Structured Outputs&#xff09;成為了大模…