> ## Documentation Index
> Fetch the complete documentation index at: https://meta.niceshare.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 接口隔离原则

> 接口隔离原则是指调用方不应被迫依赖自己用不到的方法。了解其1996年起源、Java 鼠标接口案例与失效边界。

<Info>
  **类别**: 原则<br />
  **类型**: 系统设计原则<br />
  **来源**: 罗伯特·C·马丁，*C++ Report* 工程笔记专栏，1996 年<br />
  **别名**: ISP；胖接口问题；角色接口；Interface Segregation Principle
</Info>

<Note>
  **快速回答** — **接口隔离原则**（Interface Segregation Principle）是指：调用方不应被迫依赖自己用不到的方法。罗伯特·C·马丁在 1996 年 *C++ Report* 专栏里把它写下来，针对的是把无关调用方绑在一起的“胖”接口。实务检验很硬：一次你永远不会调用的改动，若仍逼你重编译、重读或重测，你依赖的契约就太宽了。
</Note>

## 什么是接口隔离原则？

接口隔离原则是一条设计规则：调用方只应依赖自己真正用到的方法，而不应依赖别人碰巧需要的那一包契约。

> 客户不应被迫依赖他们并不使用的接口。

这不是在求你把类写小。同一个对象仍可以干许多活。这条原则管的是*可见性*：每个调用方应只看见一张瘦、内聚的脸，哪怕脸后面的对象很大。

日常图像是学校出行表。二十分钟的博物馆参观，不需要过夜医疗同意、银行资料或校车驾照。这些字段若印在同一张纸上，每位家长都得跳过——或填错。法务一改过夜条款，短途表也得重印。厨房改了一道这桌根本没点的菜。

在代码里，一个同时要求 `work()`、`eat()`、`sleep()` 的 “Worker” 类型，对机器人做的是同一件事。空方法、`UnsupportedOperationException`、以及“别在这里调用”的注释，就是伤。[单一职责原则](/zh/principles/single-responsibility)问的是：一个模块是否有太多变更理由。这条原则问的是：某一个*调用方*是否被出示了太多那些理由。

### 接口隔离原则的三层理解

* **入门**：别把打不开的工具箱塞给人。他们只想钉一幅画，开瓶器不是他们的事——直到开瓶器一改，整条工具腰带都得换发。
* **实践**：按谁来调用拆接口，而不是按谁来实现。空方法和“不支持”就是气味。胖对象可以同时实现几张瘦脸。
* **进阶**：调用方会把变更反向推到自己看见的契约上。胖接口因此把*调用方彼此*耦合起来。在静态语言里，这是一次重编译。在架构尺度上，仍是马丁后来那句话：依赖一个装了你用不到的东西的模块，是有害的。

## 起源

接口隔离原则起于静态类型面向对象代码里的编译与耦合问题，不是“接口要小”的口号。

**罗伯特·C·马丁**在 *The Interface Segregation Principle*（*C++ Report* 工程笔记专栏的第四篇，**1996 年**）里把它写下来。上一篇（**1996 年 5 月**）是依赖倒置原则。这篇专栏的工作定义，就是上面那句引文。马丁针对的是“胖”或“被污染”的接口：一个类的方法可以分成几组，每组服务不同的调用方。有些对象确实需要一张不内聚的表面。他主张：调用方不必把那张表面当成一个类型来认识。

文中第一处伤是一扇能上锁、解锁、报告开关的安全门 `Door`。`TimedDoor` 还需要超时报警，于是设计者把 `TimerClient` 推到 `Door` 上。不需要计时的门，从此继承一个必须写成空操作的 `TimeOut`。更糟的是，定时器的一处修正——加上超时编号，免得门关上再打开后误报——会迫使所有 `Door` 的调用方重编译。马丁的诊断：分开的调用方，就该有分开的接口。父类型上的空实现虚函数，也违反[里氏替换原则](/zh/principles/liskov-substitution)。

文中更大的例子是 ATM 用户界面。存款、取款、转账各自调用同一个 `UI` 类型的不同切片。一种交易强加给 `UI` 的改动，会打到其他所有交易。修法是拆成 `DepositUI`、`WithdrawalUI`、`TransferUI`，再由具体 UI 多重继承。若为了“一处就能拿到全部”又把这些脸包回一个头文件，拆掉的耦合会重新焊上。

马丁在《敏捷软件开发：原则、模式与实践》（Prentice Hall，**2003 年**）里用配套口号重述：多个面向客户的接口，好过一个通用接口。大约 **2004 年**，**Michael Feathers** 把马丁的“前五条”排成 SOLID 口诀。接口隔离原则是其中的 “I”。在《架构整洁之道》（Prentice Hall，**2017 年**，第 10 章）里，马丁把同一警告扩到类文件之外：依赖一个装了你用不到的东西的模块，是有害的。**Martin Fowler** 的*角色接口*从调用方一侧说出同一刀：接口应描述调用方需要的角色，而不是它所交谈的整个对象。

后来的讲授常把最初的咨询洞察放在施乐：一台打印机的 `Job` 类把打印、装订、传真混在一起。1996 年那篇论文本身并未点名这位客户。公开的展品是计时门和 ATM 界面。

## 核心要点

接口隔离原则在几种调用方共用一个对象、却并不共用同一批方法时，才真正值钱。它不是禁止丰富的对象。它是一条规则：哪些方法，你不许逼一个陌生人去编译。

<Steps>
  <Step title="调用方会把变更反向推到自己看见的东西上">
    我们通常担心接口一改会弄坏用户。马丁多指出一股反向力：用户可以逼接口改，胖类型的其他用户就得付钱。若薪酬和实习生名录共用一个“员工门户”类型，税务字段就能让实习生应用重编。[关注点分离](/zh/principles/separation-of-concerns)拆的是工作。这条原则拆的是看见这些工作的*窗口*。
  </Step>

  <Step title="空方法是招认，不是设计">
    机器人用抛异常实现 `eat()`，或鸟把 `fly()` 写成空操作，并不是“特化的工人”。那是过于庞大的父类型生出的孩子。这些窟窿也破坏可替换性：胖类型的调用方不能信任这个方法。把脸切开，让每个实现者都能兑现自己公布的每一条方法。
  </Step>

  <Step title="对象可以胖，脸必须瘦">
    TimedDoor 仍然既要跟门说话，也要跟定时器说话。马丁的答法是适配器对象，或对两个瘦基类型多重继承。同一个对象可以戴几顶帽子。调用方应拿一顶，而不是整架衣帽架。社团里一个人可以既是财务又管钥匙。零食轮值只应依赖“带零食”，不应依赖银行登录。
  </Step>

  <Step title="总是一起走的方法，不要拆">
    目标不是每个方法一张接口。为同一理由而变、且同一批调用方总是一起用的方法，应留在同一张脸上。[YAGNI](/zh/principles/yagni-principle)适用：在第二个调用方真正需要另一切片之前，不要发明角色。过度隔离会走出一座迷宫，类型虽多，移动仍是一块。
  </Step>
</Steps>

## 应用场景

当一个对象或表格服务几类受众，且一类受众的改动不应落到其他受众头上时，使用接口隔离原则。不要用它去打碎每个调用方本来就整套使用的内聚工具箱。

<CardGroup cols={2}>
  <Card title="在下一个调用方到来之前，先切开已公布的脸">
    若打印作业从不装订，就别让它们对着 `staple()` 编译。若只读报表从不保存，就别让它实现 `save()`。给每个调用方一个它能兑现的角色。[开放封闭原则](/zh/principles/open-closed)随后才有槽位，新调用方可填进去，而不必重开旧的。
  </Card>

  <Card title="写一份替补真正接得住的职务或义工角色">
    一张“家长义工”表若同时要驾照、餐饮卫生证和过夜医疗同意，只会吓走只带蛋糕的人。先列出*这一趟*轮值必须做的事，再只问那些。必须跳过一半表格的副手，不是报名成功，是被用不到的字段挡住了。
  </Card>

  <Card title="把公务表格按角色来印，而不是印成一份总包">
    短期签证、借书证、楼宇通行证，不该共用一份“任何公务申请”PDF。公民用多跑一趟和交错附件来付这笔错配。若有些申请人永远用不到某个字段，它就不该出现在他们的父表格上。用拆表来藏，而不是用八磅字写“如不适用请跳过”。
  </Card>

  <Card title="别把管理权力放在日常表面上">
    实习生门户若加载薪酬、董事会议纪要和生产紧急关闭开关，违的是同一条规则。只依赖角色需要的切片。这是把[最小权限](/zh/principles/least-privilege)用在知识上，而不只是用在权限上。关闭开关的一次改动，不应重发实习生首页。
  </Card>
</CardGroup>

## 经典案例

接口隔离原则作为公开 API 的带编号窗口，是 **Java 的鼠标事件监听器**——不是声称一个 GUI 包等于全部耦合理论。

**JDK 1.1**（**1997 年 2 月 19 日**）用委托取代旧的 AWT 继承式事件模型：你实现一个监听器并注册它。鼠标流量是故意切开的。`MouseListener` 收五个按键与边界方法：`mouseClicked`、`mousePressed`、`mouseReleased`、`mouseEntered`、`mouseExited`。`MouseMotionListener` 收两个运动方法：`mouseMoved` 和 `mouseDragged`。O'Reilly 的 *Java AWT Reference* 用数字说出理由：这一刀让你监听点击，而不被**成千上万**次运动事件打扰。这一刀是原则在工作。从不跟踪运动的调用方，不依赖运动。

剩下的点击接口仍然胖。多数教程示例只关心五个方法里的一个，其余四个写成空。JDK 自己的答法，同样**始于 1.1**，是 `MouseAdapter`：方法体为空的抽象类，你只覆盖关心的事件。Oracle 的 Java SE 文档至今仍这么写。截至 **Java SE 26**（**2026 年**），五方法接口和适配器都还在发货。`WindowListener` 用**七**个方法和一个 `WindowAdapter` 重复同一模式。

适配器是便利，不是被隔离的契约。调用方仍对着一个列出自己永不调用的方法的类型来编译。若给 `MouseListener` 加上第六个鼠标方法，每个直接实现者都得改。Java **8**（**2014 年 3 月**）加入了默认方法，本可以把空实现放到接口上。已公布的监听器类型并未按这个来重做。新设计该抄的是 1997 年那一刀运动/按键切开，而不是适配器补丁：在印出空方法之前，按调用方需要来拆。边界说明：必须同时处理按下、拖动、松开的绘图工具，应把这些方法留在同一张脸上。失效的是把无关受众混在一起的契约，不是每一个方法数大于一的接口。

## 边界与失效场景

当切开比调用方更细时，接口隔离原则失效。若每个调用方总是一起使用 `lock`、`unlock` 和 `isOpen`，三个单方法类型只增加导航，并不去掉耦合。这些方法是一个角色。留在同一张脸上。

它也会在已公布的胖类型上作为补救而失效。`MouseListener` 就是展品：适配器把空方法盖住，按键事件该切的那一刀从未真下。把 1997 年的适配器称作“模式”，并不使它适合抄进新 API。抄运动/按键那一刀，不要抄五个空桩。

常见误用是每个方法一张接口，然后一堆仍会一起变的类型。那是仪式，不是隔离。另一种误用是把这条原则当成单一职责原则的换名。职责管的是模块为何而变。隔离管的是*调用方*被迫看见什么。一个模块可以只有一个变更理由，却仍把同一扇胖窗出示给三类无关调用方。出乎意料的空方法也和[快速失败](/zh/principles/fail-fast)作对：程序确实失败了，却失败在胖类型本已广告为平常的一次调用上。

## 常见误区

中文名会撞上“每个接口一个方法”、撞上“只有带 `interface` 关键字的语言才算”，也撞上把适配器当成切开的替代。

<AccordionGroup>
  <Accordion title="接口隔离原则就是每个接口一个方法">
    马丁的 ATM 切开是按*调用方*，不是按方法个数。存款需要一张存款脸，上面可以有好几条存款消息。同一批调用方总是一起用的方法，应留在一起。一个谁也不需要单独拆出的单方法类型，不是成功。那是镀金之后剩下的灰。
  </Accordion>

  <Accordion title="接口隔离原则只适用于面向对象的接口">
    马丁 1996 年的专栏用 C++ 抽象类，因为那是这份杂志。同一形状出现在 API、公文、门户和职务说明里。若一个槽位写着“任何义工”“任何附件”“任何员工”，顶替者不该被迫携带这个槽位从不用的字段或模块。微服务为了一个函数去导入胖共享库，是没有 class 关键字的同一种失败。
  </Accordion>

  <Accordion title="空适配器和默认方法可以让胖接口过关">
    适配器和默认方法减少打字。它们不缩小契约。调用方仍依赖一个列出自己忽略的方法的类型，以后再加东西仍然会涟漪。空方法体是推迟的切开。若一个角色不用这个方法，这个角色的类型就不该提到这个方法。
  </Accordion>
</AccordionGroup>

## 相关概念

这些页面挨着同一个问题：如何让几类受众使用同一个对象或表格，而不让每一类受众背上别人的行李。

<CardGroup cols={3}>
  <Card title="单一职责原则" href="/zh/principles/single-responsibility">每个模块一个变更理由。这条原则接着问：某一个调用方被迫看见了其中哪些理由。</Card>
  <Card title="里氏替换原则" href="/zh/principles/liskov-substitution">胖父类型上的空方法或不支持方法，既是替换窟窿，也是隔离失败。</Card>
  <Card title="开放封闭原则" href="/zh/principles/open-closed">新调用方应插进瘦槽位。胖槽位会在新人到来时重开旧人。</Card>
  <Card title="关注点分离" href="/zh/principles/separation-of-concerns">把不同决定分开。隔离是这些决定如何被*出示*给不同调用方。</Card>
  <Card title="YAGNI 原则" href="/zh/principles/yagni-principle">在第二个调用方需要另一切片之前，不要发明角色。过度拆分是投机架构。</Card>
  <Card title="最小权限原则" href="/zh/principles/least-privilege">只依赖你需要的访问。这条原则是同一刀切在方法与模块上，而不只切在权限上。</Card>
</CardGroup>

## 一句话总结

<Tip>
  若调用方必须实现、导入或跳过自己永远不会用的方法，契约就太宽了——按角色切开脸，让胖对象去戴不止一顶帽子。
</Tip>
