Skip to main content
类别: 原则
类型: 系统设计原则
来源: 伯特兰·迈耶,《面向对象软件构造》,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 ParnasCommunications of the ACM15 卷 12 期:1053–1058)主张:按最可能变化的设计决策来分解系统——信息隐藏,而不只是把数据包起来。1996 年Alistair Cockburn 写出 Protected Variations(受保护的变异):找出预见的变异,在周围放稳定接口。Craig LarmanIEEE Software 18(3): 89–91(2001 年 5/6 月)里,把开放封闭、受保护变异和帕纳斯的隐藏当成同一条原则的不同名字。 大约 2004 年Michael Feathers 把马丁的“前五条”排成 SOLID 口诀。开放封闭原则是其中的 “O”。包装让名字出了名。机制并没有被冻结。2014 年 5 月 12 日,马丁写道:插件系统——Eclipse、Vim、Minecraft——是这条原则的“最终完成”:核心不指向插件,插件指向核心。
软件实体(类、模块、函数等)应对扩展开放,对修改封闭。

核心要点

开放封闭原则真正值钱的时候,是下一个差异很可能出现,而且你能在它到来之前叫出这种差异的种类。它不是禁止编辑。它是一条关于编辑应落在何处的规则。
1

封闭,是让客户能信任一份契约

模块封闭,意味着别人可以拿去用,而不必盯着你改写。公开的形状——接口、表格、插座——足够静止,他们的工作才不会跟着碎。如果每个新请求都逼你重开同一个函数,那些客户绑的是你的私有决定,不是契约。封闭是为了他们,不是作者的奖杯。
2

开放,是让新行为作为新零件到来

扩展是加代码、插件、处理程序或子类——不是在昨天的逻辑里再插一条分支。商店用新适配器接入一种支付方式,是开放。每来一个卡品牌就重开结账文件,不是。旧零件继续工作,因为你没有去凿它。
3

铰链是一个预见到的变异点

你封不住未知。你封的是能叫出名字的差异:图形怎么画、税怎么算、社团怎么办。把一扇稳定的门放在那里。关注点分离告诉你哪些决策该分开。开放封闭原则告诉你其中哪些决策该可插拔。叫不出变异,就等。过早的铰链,正是违反 YAGNI 的方式。
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 并不比几条诚实的分支更简单。

迪米特法则

只跟直接邻居说话。把内部泄漏出去的封闭模块,并不封闭。

一句话总结

下一个情况若是你已经叫得出的种类,就在稳定的门后面加一块新零件——不要为每一盏灯重开墙。