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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;成为了大模…