> ## 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.

# 开放封闭原则

> 开放封闭原则是指系统应对扩展开放、对修改封闭，用新代码增加行为，而不是改已经能工作的部分。了解迈耶起源、WordPress 插件案例与边界。

<Info>
  **类别**: 原则<br />
  **类型**: 系统设计原则<br />
  **来源**: 伯特兰·迈耶，《面向对象软件构造》，Prentice Hall，1988 年<br />
  **别名**: OCP；开闭原则；Open/Closed Principle
</Info>

<Note>
  **快速回答** — **开放封闭原则**（Open-Closed Principle）是指：已经能工作的模块应保持可被直接使用，新行为加在旁边，而不是凿进旧文件里。伯特兰·迈耶在 1988 年命名了这一想法；罗伯特·C·马丁后来把它说成“对扩展开放，对修改封闭”。实务检验很硬：新情况应落成新零件，而不是再去改一份已经能工作的代码。
</Note>

## 什么是开放封闭原则？

开放封闭原则是一条设计规则：软件实体——或任何结构相似的系统——应通过扩展接受新行为，而不是改写客户已经依赖的那些部分。

> 若一个模块仍可供扩展，就说它是开放的……若其他模块可以使用它，就说它是封闭的。

日常图像是墙上的插座。你不会每买一盏灯就重布一次家里的电线。插座是封闭的：它的形状是一份别人可以信任的契约。插座也是开放的：水壶、充电器或收音机都能插上去，而不必改墙。

在代码里，这道铰链是接口、钩子或插件槽。一长串 `if`，每来一种新税、新支付方式或新学生类型就重开一次，形状正好相反。[单一职责原则](/zh/principles/single-responsibility)帮你把“一个变更理由”隔离出来。开放封闭原则接着问：把这个理由放在一扇稳定的门后面，好让昨天的客户不必搬家。

迈耶 1988 年的版本靠继承：已编译的类对客户封闭，后代再加功能。1990 年代，马丁等人把同一道铰链改写成抽象接口和插件。名字还在。机制从“去继承旧模块”变成“依赖一份契约，再加一个新的实现者”。

### 开放封闭原则的三层理解

* **入门**：新情况来了，先问能不能加一块，而不是去改旧的。如果每多一盏灯都要开墙，插座一开始就没设计好。
* **实践**：点名本季真正预期的变异——支付类型、报表格式、社团种类——在它周围放一个稳定接口。然后把新情况做成新的处理程序。不要给叫不出名字的未来预留插槽；那场架属于[YAGNI](/zh/principles/yagni-principle)。
* **进阶**：你只能针对*已经预见到的*变异把模块封闭起来。铰链猜错了，会得到一套每次变更都还得拜访的空框架。完全不设铰链，每次变更又变成散弹枪式修改。这条原则是在赌下一个差异会落在哪里，不是发誓永远不碰文件。

## 起源

开放封闭原则起于模块化难题，不是贴在团队墙上的口号。

**伯特兰·迈耶**当时在做 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——是这条原则的“最终完成”：核心不指向插件，插件指向核心。

> 软件实体（类、模块、函数等）应对扩展开放，对修改封闭。

## 核心要点

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

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

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

  <Step title="铰链是一个预见到的变异点">
    你封不住未知。你封的是能叫出名字的差异：图形怎么画、税怎么算、社团怎么办。把一扇稳定的门放在那里。[关注点分离](/zh/principles/separation-of-concerns)告诉你哪些决策该分开。开放封闭原则告诉你其中哪些决策该*可插拔*。叫不出变异，就等。过早的铰链，正是违反 [YAGNI](/zh/principles/yagni-principle) 的方式。
  </Step>

  <Step title="不要把封住的文件当成封住的缺陷">
    “对修改封闭”不是发誓永不修补漏洞、安全问题或错误抽象。不得为客户改变*行为*的模块，仍可以接受修复。若契约本身错了，你就改契约，并承担代价。设计已经烂了还装文件神圣，是装神弄鬼，不是原则。
  </Step>
</Steps>

## 应用场景

当一类情况会继续增长、而旧情况还应继续工作时，再用开放封闭原则。还没理解的设计，不要用它来冻结。

<CardGroup cols={2}>
  <Card title="加一种新作业类型，而不是一团新乱麻">
    助教每来一份新作业就去改那份 400 行的评分脚本，等于每次开墙。点名共同契约——输入、评分标准、输出——把每次作业做成小处理程序。旧脚本保持封闭。新的一周是新文件。[KISS](/zh/principles/kiss-principle) 仍适用：契约要短。
  </Card>

  <Card title="接入一种支付或税务规则">
    结账、工资和税务引擎会在“每种新方法再加一个 `if`”里腐烂。发布一个适配器接口。把 Apple Pay、地区增值税或学生折扣做成新的实现者。单独测新零件。不要因为动了旧分支，就重测整台收银机。
  </Card>

  <Card title="写一份新社团能加入的章程">
    学校或社区团体常常每出现一个机器人社或合唱团，就重写一遍章程。写下*办社规则*——谁能发起、钱怎么报、何时活动——把社团名单放在附录。章程封闭。成员开放。家庭值日表也一样：格子形状固定，名字可以换。
  </Card>

  <Card title="让公共表格稳住，让附表去长">
    报税、申请资助、建筑许可在真正好用时已经是这样：短的核心表，再加上特殊情况的附表。加一张新附表，不该重写第一页。如果每个例外都逼整本小册子再版，市民和办事员都在为缺失的铰链付钱。
  </Card>
</CardGroup>

## 经典案例

开放封闭原则作为操作选择的有数字窗口，是 **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](/zh/principles/yagni-principle) 和[迭代改进](/zh/principles/iterate-and-improve)。在第二、第三个相似情况之后再关铰链，不要在第一个之前。

它也会在必须修改并重新认证的制品上失效。医疗器械、航空程序、法院已经解释过的法律表格，不能靠非正式插件生长。那里的“封闭”是监管，扩展仍须经过新的批准版本。把禁止的分叉叫成“对扩展开放”，是类别错误。

常见误用是把继承当反射。迈耶 1988 年的答案是子类。马丁后来的答案是插件和抽象接口。共享了错误父类的深继承树，不是开放封闭设计，是你以后还得重开的层级。另一种误用是把 [DRY](/zh/principles/dry-principle) 理解成“一行都不要复制，永远加策略对象”。五六行案例的重复，有时比过早的铰链更便宜。

## 常见误区

这个中文名容易撞上“永远别改旧代码”“必须用继承”，以及把 SOLID 当成给类做性格测验。

<AccordionGroup>
  <Accordion title="对修改封闭，就是永远不改现有文件">
    漏洞、安全问题和错误契约仍要修。封闭是相对于*某一行为的客户*：你加了一个他们不用的情况，他们不该被迫搬家。若公开契约本身错了，你就改它，并付涟漪的账。不能打补丁的文件不是封闭，是被遗弃。
  </Accordion>

  <Accordion title="开放封闭原则就是继承，或只属于面向对象代码">
    迈耶第一版用继承，因为他当时在教这件工具。插件钩子、函数表、配置，甚至纸质附录，形状都一样：稳定的门，旁边的新零件。马丁 2014 年的要点是，插件架构是这条原则的完整尺寸。墙上的插座不是子类。
  </Accordion>

  <Accordion title="每个类在第一天就该开放封闭">
    你没见过的变异，封不住。投机的策略对象、空的插件 API、“以防万一”的框架，是团队被插座淹死的方式。等第二个真实情况。再抽出铰链。在那之前，清楚的 `if` 是诚实的。原则是对预见变化的回应，不是对每个新文件收税。
  </Accordion>
</AccordionGroup>

## 相关概念

这些页面挨着同一个问题：怎样改变系统，又不摇晃已经能工作的一切。

<CardGroup cols={3}>
  <Card title="单一职责原则" href="/zh/principles/single-responsibility">每个模块一个变更理由。开放封闭原则决定这个理由*允许落在哪里*。</Card>
  <Card title="关注点分离" href="/zh/principles/separation-of-concerns">把不同决策分开。可插拔需要先有干净的切口，才需要插件槽。</Card>
  <Card title="YAGNI 原则" href="/zh/principles/yagni-principle">变异成真之前，不要去建铰链。没有 YAGNI 的开放封闭会变成投机架构。</Card>
  <Card title="DRY 原则" href="/zh/principles/dry-principle">一个事实一份表述。过度 DRY 会逼出假铰链；不足 DRY 则反复重开同一文件。</Card>
  <Card title="KISS 原则" href="/zh/principles/kiss-principle">让契约保持短。臃肿的插件 API 并不比几条诚实的分支更简单。</Card>
  <Card title="迪米特法则" href="/zh/principles/law-of-demeter">只跟直接邻居说话。把内部泄漏出去的封闭模块，并不封闭。</Card>
</CardGroup>

## 一句话总结

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