接口职责单一性指其须表达清晰、稳定、可独立演化的业务意图,决定实现类的可测性、可替换性与复用性;应从“做什么”出发设计,聚焦同一上下文目标,避免职责混杂或伪单一,优先组合而非膨胀接口。

接口的职责单一性不是“少写几个方法”这么简单,而是要让接口表达一个清晰、稳定、可独立演化的业务意图。它直接决定后续实现类是否容易测试、替换和复用。
从“做什么”出发,而不是“怎么写”
设计前先问:这个接口要解决哪个具体问题?谁会用它?它变化的原因是什么?
- 比如“用户管理”太宽泛,拆成 UserAuthenticator(登录/登出)、UserProfileReader(读取资料)、UserProfileUpdater(更新资料)更合理
- 避免出现“UserManager”这种名字——它暗示既要认证、又要查资料、还要发通知,职责已混杂
- 如果某个接口里同时有
save()和sendEmail(),大概率违反了单一职责;邮件发送应交给 EmailService
方法粒度要匹配职责边界
一个接口可以有多个方法,但所有方法必须服务于同一个上下文目标。
-
ContentPublisher 可以包含
publish()、unpublish()、schedule()—— 它们都在“内容发布生命周期”内 - 但不应加入
renderHtml()或logToDb(),这些属于渲染层或日志层,职责已外溢 - 方法命名要体现意图,而非技术动作。用
reserveSeat()而不是updateStatus();后者无法说明在干什么
警惕“伪单一”:接口小 ≠ 职责单
把大接口机械拆成一堆单方法接口,反而增加调用成本,也不等于真正单一。
- 例如为每个数据库操作单独建接口:CreateUserRepo、ReadUserRepo、UpdateUserRepo……这其实是按动词拆分,不是按职责
- 真正的职责是“管理用户数据”,所以 UserRepository 包含增删改查是合理的,只要不掺杂权限校验或缓存刷新逻辑
- 判断标准不是方法数量,而是“如果业务规则变了(比如新增实名要求),哪些方法会一起改?”——一起改的,就该在一个接口里
用组合代替膨胀,用继承明确语义
当需要更大能力时,不要往原接口加方法,而应通过组合或继承构建新契约。
- 比如 AdminUserRepository 继承 UserRepository,并额外声明
banUser()和exportReport()—— 明确表达了“管理员视角的用户管理”这一新职责 - 或者让某个服务类同时持有 UserRepository 和 NotificationService,由它协调流程,而不是让 UserRepository 负责发通知
- 组合优先于继承;继承用于表达“是…的一种”,组合用于表达“拥有…的能力”

















