Skip to main content
類別: 原則
類型: 系統設計原則
來源: 羅伯特·C·馬丁,C++ Report 工程筆記專欄,1996 年
別名: ISP;胖介面問題;角色介面;Interface Segregation Principle
快速回答介面隔離原則(Interface Segregation Principle)是指:呼叫方不應被迫依賴自己用不到的方法。羅伯特·C·馬丁在 1996 年 C++ Report 專欄裡把它寫下來,針對的是把無關呼叫方綁在一起的「胖」介面。實務檢驗很硬:一次你永遠不會呼叫的改動,若仍逼你重編譯、重讀或重測,你依賴的契約就太寬了。

什麼是介面隔離原則?

介面隔離原則是一條設計規則:呼叫方只應依賴自己真正用到的方法,而不應依賴別人碰巧需要的那一包契約。
客戶不應被迫依賴他們並不使用的介面。
這不是在求你把類別寫小。同一個物件仍可以幹許多活。這條原則管的是可見性:每個呼叫方應只看見一張瘦、內聚的臉,哪怕臉後面的物件很大。 日常圖像是學校出遊表。二十分鐘的博物館參觀,不需要過夜醫療同意、銀行資料或校車駕照。這些欄位若印在同一張紙上,每位家長都得跳過——或填錯。法務一改過夜條款,短途表也得重印。廚房改了一道這桌根本沒點的菜。 在程式碼裡,一個同時要求 work()eat()sleep() 的 「Worker」類型,對機器人做的是同一件事。空方法、UnsupportedOperationException、以及「別在這裡呼叫」的註解,就是傷。單一職責原則問的是:一個模組是否有太多變更理由。這條原則問的是:某一個呼叫方是否被出示了太多那些理由。

介面隔離原則的三層理解

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

起源

介面隔離原則起於靜態型別物件導向程式碼裡的編譯與耦合問題,不是「介面要小」的口號。 羅伯特·C·馬丁The Interface Segregation PrincipleC++ Report 工程筆記專欄的第四篇,1996 年)裡把它寫下來。上一篇(1996 年 5 月)是依賴反轉原則。這篇專欄的工作定義,就是上面那句引文。馬丁針對的是「胖」或「被污染」的介面:一個類別的方法可以分成幾組,每組服務不同的呼叫方。有些物件確實需要一張不內聚的表面。他主張:呼叫方不必把那張表面當成一個類型來認識。 文中第一處傷是一扇能上鎖、解鎖、回報開關的安全門 DoorTimedDoor 還需要逾時警報,於是設計者把 TimerClient 推到 Door 上。不需要計時的門,從此繼承一個必須寫成空操作的 TimeOut。更糟的是,計時器的一處修正——加上逾時編號,免得門關上再打開後誤報——會迫使所有 Door 的呼叫方重編譯。馬丁的診斷:分開的呼叫方,就該有分開的介面。父類型上的空虛擬函式,也違反里氏替換原則 文中更大的例子是 ATM 使用者介面。存款、提款、轉帳各自呼叫同一個 UI 類型的不同切片。一種交易強加給 UI 的改動,會打到其他所有交易。修法是拆成 DepositUIWithdrawalUITransferUI,再由具體 UI 多重繼承。若為了「一處就能拿到全部」又把這些臉包回一個標頭檔,拆掉的耦合會重新焊上。 馬丁在《敏捷軟體開發:原則、模式與實務》(Prentice Hall,2003 年)裡用配套口號重述:多個面向客戶的介面,好過一個通用介面。大約 2004 年Michael Feathers 把馬丁的「前五條」排成 SOLID 口訣。介面隔離原則是其中的 「I」。在《架構整潔之道》(Prentice Hall,2017 年,第 10 章)裡,馬丁把同一警告擴到類別檔之外:依賴一個裝了你用不到的東西的模組,是有害的。Martin Fowler角色介面從呼叫方一側說出同一刀:介面應描述呼叫方需要的角色,而不是它所交談的整個物件。 後來的講授常把最初的顧問洞察放在全錄:一台印表機的 Job 類別把列印、裝訂、傳真混在一起。1996 年那篇論文本身並未點名這位客戶。公開的展品是計時門和 ATM 介面。

核心要點

介面隔離原則在幾種呼叫方共用一個物件、卻並不共用同一批方法時,才真正值錢。它不是禁止豐富的物件。它是一條規則:哪些方法,你不許逼一個陌生人去編譯。
1

呼叫方會把變更反向推到自己看見的東西上

我們通常擔心介面一改會弄壞使用者。馬丁多指出一股反向力:使用者可以逼介面改,胖類型的其他使用者就得付錢。若薪酬和實習生名錄共用一個「員工入口」類型,稅務欄位就能讓實習生應用重編。關注點分離拆的是工作。這條原則拆的是看見這些工作的窗口
2

空方法是招認,不是設計

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

物件可以胖,臉必須瘦

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

總是一起走的方法,不要拆

目標不是每個方法一張介面。為同一理由而變、且同一批呼叫方總是一起用的方法,應留在同一張臉上。YAGNI適用:在第二個呼叫方真正需要另一切片之前,不要發明角色。過度隔離會走出一座迷宮,類型雖多,移動仍是一塊。

應用場景

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

在下一個呼叫方到來之前,先切開已公布的臉

若列印作業從不裝訂,就別讓它們對著 staple() 編譯。若唯讀報表從不儲存,就別讓它實作 save()。給每個呼叫方一個它能兌現的角色。開放封閉原則隨後才有槽位,新呼叫方可填進去,而不必重開舊的。

寫一份替補真正接得住的職務或志工角色

一張「家長志工」表若同時要駕照、餐飲衛生證和過夜醫療同意,只會嚇走只帶蛋糕的人。先列出這一趟輪值必須做的事,再只問那些。必須跳過一半表格的副手,不是報名成功,是被用不到的欄位擋住了。

把公務表格按角色來印,而不是印成一份總包

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

別把管理權力放在日常表面上

實習生入口若載入薪酬、董事會議紀錄和生產緊急關閉開關,違的是同一條規則。只依賴角色需要的切片。這是把最小權限用在知識上,而不只是用在權限上。關閉開關的一次改動,不應重發實習生首頁。

經典案例

介面隔離原則作為公開 API 的帶編號窗口,是 Java 的滑鼠事件監聽器——不是聲稱一個 GUI 套件等於全部耦合理論。 JDK 1.11997 年 2 月 19 日)用委派取代舊的 AWT 繼承式事件模型:你實作一個監聽器並註冊它。滑鼠流量是故意切開的。MouseListener 收五個按鍵與邊界方法:mouseClickedmousePressedmouseReleasedmouseEnteredmouseExitedMouseMotionListener 收兩個運動方法:mouseMovedmouseDragged。O’Reilly 的 Java AWT Reference 用數字說出理由:這一刀讓你監聽點擊,而不被成千上萬次運動事件打擾。這一刀是原則在工作。從不追蹤運動的呼叫方,不依賴運動。 剩下的點擊介面仍然胖。多數教學示例只關心五個方法裡的一個,其餘四個寫成空。JDK 自己的答法,同樣始於 1.1,是 MouseAdapter:方法體為空的抽象類別,你只覆蓋關心的事件。Oracle 的 Java SE 文件至今仍這麼寫。截至 Java SE 262026 年),五方法介面和轉接器都還在出貨。WindowListener個方法和一個 WindowAdapter 重複同一模式。 轉接器是便利,不是被隔離的契約。呼叫方仍對著一個列出自己永不呼叫的方法的類型來編譯。若給 MouseListener 加上第六個滑鼠方法,每個直接實作者都得改。Java 82014 年 3 月)加入了預設方法,本可以把空實作放到介面上。已公布的監聽器類型並未按這個來重做。新設計該抄的是 1997 年那一刀運動/按鍵切開,而不是轉接器補丁:在印出空方法之前,按呼叫方需要來拆。邊界說明:必須同時處理按下、拖曳、放開的繪圖工具,應把這些方法留在同一張臉上。失效的是把無關受眾混在一起的契約,不是每一個方法數大於一的介面。

邊界與失效場景

當切開比呼叫方更細時,介面隔離原則失效。若每個呼叫方總是一起使用 lockunlockisOpen,三個單方法類型只增加導覽,並不去掉耦合。這些方法是一個角色。留在同一張臉上。 它也會在已公布的胖類型上作為補救而失效。MouseListener 就是展品:轉接器把空方法蓋住,按鍵事件該切的那一刀從未真下。把 1997 年的轉接器稱作「模式」,並不使它適合抄進新 API。抄運動/按鍵那一刀,不要抄五個空樁。 常見誤用是每個方法一張介面,然後一堆仍會一起變的類型。那是儀式,不是隔離。另一種誤用是把這條原則當成單一職責原則的換名。職責管的是模組為何而變。隔離管的是呼叫方被迫看見什麼。一個模組可以只有一個變更理由,卻仍把同一扇胖窗出示給三類無關呼叫方。出乎意料的空方法也和快速失敗作對:程式確實失敗了,卻失敗在胖類型本已廣告為平常的一次呼叫上。

常見誤區

中文名會撞上「每個介面一個方法」、撞上「只有帶 interface 關鍵字的語言才算」,也撞上把轉接器當成切開的替代。
馬丁的 ATM 切開是按呼叫方,不是按方法個數。存款需要一張存款臉,上面可以有好幾條存款訊息。同一批呼叫方總是一起用的方法,應留在一起。一個誰也不需要單獨拆出的單方法類型,不是成功。那是鍍金之後剩下的灰。
馬丁 1996 年的專欄用 C++ 抽象類別,因為那是這份雜誌。同一形狀出現在 API、公文、入口和職務說明裡。若一個槽位寫著「任何志工」「任何附件」「任何員工」,頂替者不該被迫攜帶這個槽位從不用的欄位或模組。微服務為了一個函式去匯入胖共用程式庫,是沒有 class 關鍵字的同一種失敗。
轉接器和預設方法減少打字。它們不縮小契約。呼叫方仍依賴一個列出自己忽略的方法的類型,以後再加東西仍然會漣漪。空方法體是推遲的切開。若一個角色不用這個方法,這個角色的類型就不該提到這個方法。

相關概念

這些頁面挨著同一個問題:如何讓幾類受眾使用同一個物件或表格,而不讓每一類受眾背上別人的行李。

單一職責原則

每個模組一個變更理由。這條原則接著問:某一個呼叫方被迫看見了其中哪些理由。

里氏替換原則

胖父類型上的空方法或不支援方法,既是替換窟窿,也是隔離失敗。

開放封閉原則

新呼叫方應插進瘦槽位。胖槽位會在新人到來時重開舊人。

關注點分離

把不同決定分開。隔離是這些決定如何被出示給不同呼叫方。

YAGNI原則

在第二個呼叫方需要另一切片之前,不要發明角色。過度拆分是投機架構。

最小權限原則

只依賴你需要的存取。這條原則是同一刀切在方法與模組上,而不只切在權限上。

一句話總結

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