本节摘要:面向对象与函数式各有纪律清单。面向对象一侧:类遵守单一职责、组合优于继承、封装内部状态、用依赖注入管理关系;函数式一侧:优先纯函数、副作用集中隔离、数据倾向不可变。本节说明两套范式并不对立——现代工程通常在服务层用对象组织协作、在逻辑层用纯函数计算,并给出混合使用的边界判断。
阅读完本节,你应当能够:
一个订单系统里,RegularOrder 类继承了 Order 基类。后来来了新需求:会员订单要叠加积分逻辑,工程师顺手建了 MemberOrder extends RegularOrder。再后来限时订单再叠一层 FlashOrder extends MemberOrder。半年后继承树四层深:改基类的一个方法,四个子类行为全部抖动;限时订单根本不该有积分,却必须覆写一堆方法来"退掉"继承来的行为;新人看着这棵树问"想加一个只免运费的订单放哪里",全场沉默。
继承的问题在这个案例里暴露完整:它把分类学当成了组合工具。继承表达"是一个"(会员订单确实是一种订单),但业务里的变化维度(是否会员、是否限时、是否免邮)是正交的,正交维度用继承表达就会组合爆炸——每个维度组合一个子类,树指数生长。解法是组合:把每个可变行为做成独立组件,订单持有需要的组件。"有一个"胜过"是一个"。
函数式一侧有另一个极端故事:一个"函数"先改全局缓存、再写数据库、再按当前时间决定分支,最后返回计算结果。没人能测试它——测试要先摆好全局状态、准备数据库、把时间调到特定点。这个函数的问题不是写法而是副作用失控:计算与影响混在一起,输出不再只取决于输入。
纪律一:单一职责落到类上。一个类只有一个引起它变化的理由。订单的定价规则变化不该波及订单的持久化代码——那说明定价与持久化该是两个类。检验句式:"这个类代表一个概念吗?"要用"和"字才能概括的类,该拆。
纪律二:组合优于继承。继承是强耦合(子类默认接受基类全部细节),且类继承在多数语言里是静态的、单通道的。组合是弱耦合、可运行时替换、可正交叠加。规则:"是一个"且行为稳定才用继承;"有一个"或维度正交必用组合。前述订单问题的组合解法(概念代码):
class Order: def __init__(self, pricing, benefits): self.pricing = pricing # 定价策略 可替换 self.benefits = benefits # 权益组件 可叠加 class MemberPricing: ... # 各维度独立 class FlashSalePricing: ... class FreeShippingBenefit: ...
纪律三:封装。内部状态私有,公开的只有表达"能做什么"的方法。公开一堆字段再让外部读写,对象就退化成了结构体——任何外部代码都可能依赖这些字段,日后重构寸步难行。接口设计从使用者的角度问:"这个类提供什么能力?"而不是"它内部装了什么?"
纪律四:依赖注入。对象需要的协作者(数据库访问、消息服务)从外部传入,不在内部创建。对照(概念代码):
# 内部创建 测试要连真数据库 改实现要动源码 class OrderService: def __init__(self): self.repo = DatabaseOrderRepository() # 注入 测试给个假仓库 换实现零改动 class OrderService: def __init__(self, repository): self.repository = repository
注入的收益在第 5.2 节展开:可测试性与可替换性同时到手。
纪律一:优先纯函数。同样输入永远同样输出、不碰外部状态。纯函数的测试是"给输入断言输出"两行;出错的推理是"输入固定输出必固定"的底气。业务不可能全纯(落库、发消息都是副作用),原则是能纯则纯:计算、转换、校验这些逻辑核心保持纯,把副作用推到边界。
纪律二:副作用集中隔离。把读写数据库、发请求、打日志这类影响外界的操作收拢到明确的边界层(仓库、网关、控制器),核心逻辑层只见纯函数。这样"哪里可能出岔子"在架构图上一目了然——副作用集中在少数咽喉要道,而不是散布全库。
纪律三:倾向不可变。数据创建后不再修改,要"变"就生成新副本。可变共享状态是并发缺陷的头号来源——两个流程同时改一个列表,执行顺序不同结果不同,复现全凭运气。不可变从根上消灭这类问题,还让"值"可以被放心传递与缓存。

接口(抽象契约)有两种正当用途:多个真实实现并存(不同数据源)、测试需要替换(注入假实现)。只有一个实现且没有测试替换需求时,接口是纯间接层——又回到第 4.3 节的 YAGNI 判断。接口命名遵守第 2.2 节的约定,能力型接口用形容词性命名。
类太大会演化成"上帝类"——什么都知道、什么都管,改任何功能都要碰它。预防性的规模信号:类超过三百到四百行、公开方法超过十五个、构造函数参数超过七个,三条中两条就该考虑拆分。拆分方向按职责线:把"变化理由相同"的成员留在原地,其余按职责迁出。
⚠️ 常见坑:教条地站队"面向对象好"或"函数式好",在纯对象项目里给每个计算都建类、在函数式项目里回避一切抽象类型。范式是工具不是信仰,成熟工程师的标志是知道每种工具的领地:协作用对象,计算用函数,副作用守边界。
💡 关键直觉:两套范式在解决同一个敌人——失控的耦合。继承的耦合是"基类改了子类抖",可变共享的耦合是"这里改了那里抖"。组合拆前一种,纯函数与不可变拆后一种。
| 维度 | 面向对象 | 函数式 |
|---|---|---|
| 组织单元 | 类与对象 | 纯函数与数据 |
| 核心纪律 | 单一职责 组合 封装 注入 | 纯函数 副作用隔离 不可变 |
| 擅长 | 协作建模 状态归属 | 计算转换 并行安全 |
| 耦合风险 | 深继承树 上帝类 | 隐式全局状态 |
| 测试形态 | 注入替身测行为 | 断言输入输出 |
下一章把视角从代码转向团队:一致性、可维护性、测试与版本控制的协作规范。