> ## 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-hant/principles/single-responsibility)問的是：一個模組是否有太多變更理由。這條原則問的是：某一個*呼叫方*是否被出示了太多那些理由。

### 介面隔離原則的三層理解

* **入門**：別把打不開的工具箱塞給人。他們只想釘一幅畫，開瓶器不是他們的事——直到開瓶器一改，整條工具腰帶都得換發。
* **實務**：按誰來呼叫拆介面，而不是按誰來實作。空方法和「不支援」就是氣味。胖物件可以同時實作幾張瘦臉。
* **進階**：呼叫方會把變更反向推到自己看見的契約上。胖介面因此把*呼叫方彼此*耦合起來。在靜態語言裡，這是一次重編譯。在架構尺度上，仍是馬丁後來那句話：依賴一個裝了你用不到的東西的模組，是有害的。

## 起源

介面隔離原則起於靜態型別物件導向程式碼裡的編譯與耦合問題，不是「介面要小」的口號。

**羅伯特·C·馬丁**在 *The Interface Segregation Principle*（*C++ Report* 工程筆記專欄的第四篇，**1996 年**）裡把它寫下來。上一篇（**1996 年 5 月**）是依賴反轉原則。這篇專欄的工作定義，就是上面那句引文。馬丁針對的是「胖」或「被污染」的介面：一個類別的方法可以分成幾組，每組服務不同的呼叫方。有些物件確實需要一張不內聚的表面。他主張：呼叫方不必把那張表面當成一個類型來認識。

文中第一處傷是一扇能上鎖、解鎖、回報開關的安全門 `Door`。`TimedDoor` 還需要逾時警報，於是設計者把 `TimerClient` 推到 `Door` 上。不需要計時的門，從此繼承一個必須寫成空操作的 `TimeOut`。更糟的是，計時器的一處修正——加上逾時編號，免得門關上再打開後誤報——會迫使所有 `Door` 的呼叫方重編譯。馬丁的診斷：分開的呼叫方，就該有分開的介面。父類型上的空虛擬函式，也違反[里氏替換原則](/zh-hant/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-hant/principles/separation-of-concerns)拆的是工作。這條原則拆的是看見這些工作的*窗口*。
  </Step>

  <Step title="空方法是招認，不是設計">
    機器人用丟出例外實作 `eat()`，或鳥把 `fly()` 寫成空操作，並不是「特化的工人」。那是過大父類型的孩子。這些窟窿也破壞可替換性：胖類型的呼叫方不能信任這個方法。把臉切開，讓每個實作者都能兌現自己公布的每一條方法。
  </Step>

  <Step title="物件可以胖，臉必須瘦">
    TimedDoor 仍然既要跟門說話，也要跟計時器說話。馬丁的答法是轉接器物件，或對兩個瘦基底類型多重繼承。同一個物件可以戴幾頂帽子。呼叫方應拿一頂，而不是整架衣帽架。社團裡一個人可以既是財務又管鑰匙。零食輪值只應依賴「帶零食」，不應依賴銀行登入。
  </Step>

  <Step title="總是一起走的方法，不要拆">
    目標不是每個方法一張介面。為同一理由而變、且同一批呼叫方總是一起用的方法，應留在同一張臉上。[YAGNI](/zh-hant/principles/yagni-principle)適用：在第二個呼叫方真正需要另一切片之前，不要發明角色。過度隔離會走出一座迷宮，類型雖多，移動仍是一塊。
  </Step>
</Steps>

## 應用場景

當一個物件或表格服務幾類受眾，且一類受眾的改動不應落到其他受眾頭上時，使用介面隔離原則。不要用它去打碎每個呼叫方本來就整套使用的內聚工具箱。

<CardGroup cols={2}>
  <Card title="在下一個呼叫方到來之前，先切開已公布的臉">
    若列印作業從不裝訂，就別讓它們對著 `staple()` 編譯。若唯讀報表從不儲存，就別讓它實作 `save()`。給每個呼叫方一個它能兌現的角色。[開放封閉原則](/zh-hant/principles/open-closed)隨後才有槽位，新呼叫方可填進去，而不必重開舊的。
  </Card>

  <Card title="寫一份替補真正接得住的職務或志工角色">
    一張「家長志工」表若同時要駕照、餐飲衛生證和過夜醫療同意，只會嚇走只帶蛋糕的人。先列出*這一趟*輪值必須做的事，再只問那些。必須跳過一半表格的副手，不是報名成功，是被用不到的欄位擋住了。
  </Card>

  <Card title="把公務表格按角色來印，而不是印成一份總包">
    短期簽證、借書證、樓宇通行證，不該共用一份「任何公務申請」PDF。公民用多跑一趟和交錯附件來付這筆錯配。若有些申請人永遠用不到某個欄位，它就不該出現在他們的父表格上。用拆表來藏，而不是用八磅字寫「如不適用請跳過」。
  </Card>

  <Card title="別把管理權力放在日常表面上">
    實習生入口若載入薪酬、董事會議紀錄和生產緊急關閉開關，違的是同一條規則。只依賴角色需要的切片。這是把[最小權限](/zh-hant/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-hant/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-hant/principles/single-responsibility">每個模組一個變更理由。這條原則接著問：某一個呼叫方被迫看見了其中哪些理由。</Card>
  <Card title="里氏替換原則" href="/zh-hant/principles/liskov-substitution">胖父類型上的空方法或不支援方法，既是替換窟窿，也是隔離失敗。</Card>
  <Card title="開放封閉原則" href="/zh-hant/principles/open-closed">新呼叫方應插進瘦槽位。胖槽位會在新人到來時重開舊人。</Card>
  <Card title="關注點分離" href="/zh-hant/principles/separation-of-concerns">把不同決定分開。隔離是這些決定如何被*出示*給不同呼叫方。</Card>
  <Card title="YAGNI原則" href="/zh-hant/principles/yagni-principle">在第二個呼叫方需要另一切片之前，不要發明角色。過度拆分是投機架構。</Card>
  <Card title="最小權限原則" href="/zh-hant/principles/least-privilege">只依賴你需要的存取。這條原則是同一刀切在方法與模組上，而不只切在權限上。</Card>
</CardGroup>

## 一句話總結

<Tip>
  若呼叫方必須實作、匯入或跳過自己永遠不會用的方法，契約就太寬了——按角色切開臉，讓胖物件去戴不止一頂帽子。
</Tip>
