類別: 原則
類型: 系統設計原則
來源: 伯特蘭·邁耶,《物件導向軟體建構》,Prentice Hall,1988 年
別名: OCP;開閉原則;Open/Closed Principle
類型: 系統設計原則
來源: 伯特蘭·邁耶,《物件導向軟體建構》,Prentice Hall,1988 年
別名: OCP;開閉原則;Open/Closed Principle
快速回答 — 開放封閉原則(Open-Closed Principle)是指:已經能工作的模組應保持可被直接使用,新行為加在旁邊,而不是鑿進舊檔案裡。伯特蘭·邁耶在 1988 年為這個想法命名;羅伯特·C·馬丁後來把它說成「對擴充開放,對修改封閉」。實務檢驗很硬:新情況應落成新零件,而不是再去改一份已經能工作的程式碼。
什麼是開放封閉原則?
開放封閉原則是一條設計規則:軟體實體——或任何結構相似的系統——應透過擴充接受新行為,而不是改寫客戶已經依賴的那些部分。若一個模組仍可供擴充,就說它是開放的……若其他模組可以使用它,就說它是封閉的。日常圖像是牆上的插座。你不會每買一盞燈就重佈一次家裡的電線。插座是封閉的:它的形狀是一份別人可以信任的契約。插座也是開放的:水壺、充電器或收音機都能插上去,而不必改牆。 在程式碼裡,這道鉸鏈是介面、鉤子或外掛槽。一長串
if,每來一種新稅、新支付方式或新學生類型就重開一次,形狀正好相反。單一職責原則幫你把「一個變更理由」隔離出來。開放封閉原則接著問:把這個理由放在一扇穩定的門後面,好讓昨天的客戶不必搬家。
邁耶 1988 年的版本靠繼承:已編譯的類別對客戶封閉,後代再加功能。1990 年代,馬丁等人把同一道鉸鏈改寫成抽象介面和外掛。名字還在。機制從「去繼承舊模組」變成「依賴一份契約,再加一個新的實作者」。
開放封閉原則的三層理解
- 入門:新情況來了,先問能不能加一塊,而不是去改舊的。如果每多一盞燈都要開牆,插座一開始就沒設計好。
- 實踐:點名本季真正預期的變異——支付類型、報表格式、社團種類——在它周圍放一個穩定介面。然後把新情況做成新的處理程式。不要給叫不出名字的未來預留插槽;那場架屬於 YAGNI。
- 進階:你只能針對已經預見到的變異把模組封閉起來。鉸鏈猜錯了,會得到一套每次變更都還得拜訪的空框架。完全不設鉸鏈,每次變更又變成散彈槍式修改。這條原則是在賭下一個差異會落在哪裡,不是發誓永遠不碰檔案。
起源
開放封閉原則起於模組化難題,不是貼在團隊牆上的口號。 伯特蘭·邁耶當時在做 Eiffel 語言與方法,於 1988 年由 Prentice Hall 出版的《物件導向軟體建構》(Object-Oriented Software Construction)裡寫下它。第一版第 23 頁要求「令人滿意的模組分解」必須給出既開放又封閉的模組。開放意味著還能加欄位或功能。封閉意味著其他模組能從一份穩定介面使用它——編譯好、放進函式庫、作為基準線發布。他當時的技術答案是繼承:一個類別可以對客戶凍結,同時仍由後代擴充,而不打擾原件。 大家常引的那句短話並不是邁耶的原文。1996 年 1 月,《C++ Report》上 羅伯特·C·馬丁把他轉述為:軟體實體「應對擴充開放,對修改封閉」。這篇文章後來進入他 2000 年的 “Design Principles and Design Patterns”,以及 2003 年 Prentice Hall 的《敏捷軟體開發:原則、模式與實務》。鉸鏈從實作繼承挪到抽象介面。新行為是滿足契約的新類別。舊原始碼不動。 底下還有兩條更老的想法。1972 年 12 月,David Parnas 在 Communications of the ACM(15 卷第 12 期:1053–1058)主張:按最可能變化的設計決策來分解系統——資訊隱藏,而不只是把資料包起來。1996 年,Alistair Cockburn 寫出 Protected Variations(受保護的變異):找出預見的變異,在周圍放穩定介面。Craig Larman 在 IEEE Software 18(3): 89–91(2001 年 5/6 月)裡,把開放封閉、受保護變異和帕納斯的隱藏當成同一條原則的不同名字。 大約 2004 年,Michael Feathers 把馬丁的「前五條」排成 SOLID 口訣。開放封閉原則是其中的 「O」。包裝讓名字出了名。機制並沒有被凍結。2014 年 5 月 12 日,馬丁寫道:外掛系統——Eclipse、Vim、Minecraft——是這條原則的「最終完成」:核心不指向外掛,外掛指向核心。軟體實體(類別、模組、函數等)應對擴充開放,對修改封閉。
核心要點
開放封閉原則真正值錢的時候,是下一個差異很可能出現,而且你能在它到來之前叫出這種差異的種類。它不是禁止編輯。它是一條關於編輯應落在何處的規則。1
封閉,是讓客戶能信任一份契約
模組封閉,意味著別人可以拿去用,而不必盯著你改寫。公開的形狀——介面、表格、插座——足夠靜止,他們的工作才不會跟著碎。如果每個新請求都逼你重開同一個函數,那些客戶綁的是你的私有決定,不是契約。封閉是為了他們,不是作者的獎盃。
2
開放,是讓新行為作為新零件到來
擴充是加程式碼、外掛、處理程式或子類別——不是在昨天的邏輯裡再插一條分支。商店用新轉接器接入一種支付方式,是開放。每來一個卡品牌就重開結帳檔案,不是。舊零件繼續工作,因為你沒有去鑿它。
3
4
不要把封住的檔案當成封住的缺陷
「對修改封閉」不是發誓永不修補漏洞、安全問題或錯誤抽象。不得為客戶改變行為的模組,仍可以接受修復。若契約本身錯了,你就改契約,並承擔代價。設計已經爛了還裝檔案神聖,是裝神弄鬼,不是原則。
應用場景
當一類情況會繼續增長、而舊情況還應繼續工作時,再用開放封閉原則。還沒理解的設計,不要用它來凍結。加一種新作業類型,而不是一團新亂麻
助教每來一份新作業就去改那份 400 行的評分腳本,等於每次開牆。點名共同契約——輸入、評分標準、輸出——把每次作業做成小處理程式。舊腳本保持封閉。新的一週是新檔案。KISS 仍適用:契約要短。
接入一種支付或稅務規則
結帳、薪資和稅務引擎會在「每種新方法再加一個
if」裡腐爛。發布一個轉接器介面。把 Apple Pay、地區增值稅或學生折扣做成新的實作者。單獨測新零件。不要因為動了舊分支,就重測整台收銀機。寫一份新社團能加入的章程
學校或社區團體常常每出現一個機器人社或合唱團,就重寫一遍章程。寫下辦社規則——誰能發起、錢怎麼報、何時活動——把社團名單放在附錄。章程封閉。成員開放。家庭值日表也一樣:格子形狀固定,名字可以換。
讓公共表格穩住,讓附表去長
報稅、申請資助、建築許可在真正好用時已經是這樣:短的核心表,再加上特殊情況的附表。加一張新附表,不該重寫第一頁。如果每個例外都逼整本小冊子再版,市民和辦事員都在為缺失的鉸鏈付錢。
經典案例
開放封閉原則作為操作選擇的有數字窗口,是 WordPress 及其外掛架構——不是聲稱部落格軟體變成了邁耶式的完美。 2004 年以前,人們用「駭客補丁」擴充 WordPress 及其前身 b2:一份說明書寫著改這個核心檔案,貼這段。升級會覆蓋貼上。定製和產品互相打架。2004 年 5 月 22 日,Matt Mullenweg 宣布 WordPress 1.2,代號 “Mingus”。功能之一是「新的外掛架構」,讓外掛可以「鉤進 WordPress 幾乎每一個動作」。這些鉤子——action 與 filter——就是擴充點。新行為住在wp-content/plugins/,不在升級會替換的那些檔案裡。
設計也改了功能歸誰。把外掛系統推進專案的 Ryan Boren 後來描述核心團隊的經驗規則:若功能對大約八成使用者沒有用,就試試做成外掛。隨 1.2 附帶的第一個例子是 Hello Dolly:一個列印歌詞的小外掛,坐在核心旁邊而不是裡面。機制就是不叫這個名字的開放封閉原則。核心給出契約。新行為是新零件。鉤子本身穩定時,舊站點升級仍可保住外掛。
後來的規模是公開的,而且是目錄計數,不是品質稽核。截至 2026 年 8 月,WordPress.org 外掛目錄自稱「超過 71,000 個免費外掛」。這個數字量的是槽有多寬。它不證明每個外掛都安全,也不證明核心本身永不改。邊界說明:WordPress 仍會為漏洞、安全和編輯器而改核心。鉤子一挪,許多外掛仍會在大版本上斷裂。還讓你去改核心的外掛,只是換了資料夾的舊補丁。案例展示的是鉸鏈——穩定的鉤子、新的檔案——不是聲稱 71,000 個附加元件的生態自動就設計良好。
邊界與失效場景
還叫不出變異時,開放封閉原則會失效。為從未到來的未來搭「靈活框架」,是典型的過度封閉:很多空插座,一盞真燈,房子卻沒人能重佈線。所以它必須挨著 YAGNI 和迭代改進。在第二、第三個相似情況之後再關鉸鏈,不要在第一個之前。 它也會在必須修改並重新認證的製品上失效。醫療器械、航空程序、法院已經解釋過的法律表格,不能靠非正式外掛生長。那裡的「封閉」是監管,擴充仍須經過新的批准版本。把禁止的分叉叫成「對擴充開放」,是類別錯誤。 常見誤用是把繼承當反射。邁耶 1988 年的答案是子類別。馬丁後來的答案是外掛和抽象介面。共享了錯誤父類別的深繼承樹,不是開放封閉設計,是你以後還得重開的層級。另一種誤用是把 DRY 理解成「一行都不要複製,永遠加策略物件」。五六行案例的重複,有時比過早的鉸鏈更便宜。常見誤區
這個中文名容易撞上「永遠別改舊程式碼」「必須用繼承」,以及把 SOLID 當成給類別做性格測驗。對修改封閉,就是永遠不改現有檔案
對修改封閉,就是永遠不改現有檔案
漏洞、安全問題和錯誤契約仍要修。封閉是相對於某一行為的客戶:你加了一個他們不用的情況,他們不該被迫搬家。若公開契約本身錯了,你就改它,並付漣漪的帳。不能打補丁的檔案不是封閉,是被遺棄。
開放封閉原則就是繼承,或只屬於物件導向程式碼
開放封閉原則就是繼承,或只屬於物件導向程式碼
邁耶第一版用繼承,因為他當時在教這件工具。外掛鉤子、函數表、設定,甚至紙本附錄,形狀都一樣:穩定的門,旁邊的新零件。馬丁 2014 年的要點是,外掛架構是這條原則的完整尺寸。牆上的插座不是子類別。
每個類別在第一天就該開放封閉
每個類別在第一天就該開放封閉
你沒見過的變異,封不住。投機的策略物件、空的外掛 API、「以防萬一」的框架,是團隊被插座淹死的方式。等第二個真實情況。再抽出鉸鏈。在那之前,清楚的
if 是誠實的。原則是對預見變化的回應,不是對每個新檔案收稅。相關概念
這些頁面挨著同一個問題:怎樣改變系統,又不搖晃已經能工作的一切。單一職責原則
每個模組一個變更理由。開放封閉原則決定這個理由允許落在哪裡。
關注點分離
把不同決策分開。可插拔需要先有乾淨的切口,才需要外掛槽。
YAGNI 原則
變異成真之前,不要去建鉸鏈。沒有 YAGNI 的開放封閉會變成投機架構。
DRY 原則
一個事實一份表述。過度 DRY 會逼出假鉸鏈;不足 DRY 則反覆重開同一檔案。
KISS 原則
讓契約保持短。臃腫的外掛 API 並不比幾條誠實的分支更簡單。
迪米特法則
只跟直接鄰居說話。把內部洩漏出去的封閉模組,並不封閉。