# 02 · 可扩展性与配置化 本篇把总则里的 **OCP(对扩展开放)** 和 **配置优先** 落成可直接套用的模式与判据, 并给出**避免过度设计**的红线——扩展性是为了"改得动",不是为了炫技。 --- ## 1. 可扩展性模式(够用就好) 下面几种模式覆盖子游戏绝大多数扩展需求。**先问"现在真的需要扩展吗",需要再用**(见 §3)。 ### 1.1 注册表 + 策略(Registry + Strategy)— 最常用 把"一族可替换的行为"抽象成**策略对象**,用**注册表**收集,运行时按上下文选一个执行。 新增行为 = 写一个策略 + 注册,**核心零改动**。 ``` Registry(注册表) ── register(strategy) ──▶ [strategyA, strategyB, ...] │ Context(只读上下文)──▶ 选择器 select(ctx) ─────────┘──▶ 命中的策略.execute(ctx) ``` - **适用**:AI 决策分级、规则变体、牌型识别族、结算规则族——"同一类事有多种做法"。 - **要点**: - 策略只依赖**只读上下文**,不反向修改全局;上下文封装它需要的权威数据。 - 有**默认策略兜底**(保证任何输入都有结果),高级策略**按需叠加**。 - 策略之间**互不知道**对方,新增不影响既有。 - **例**:分级决策框架——`Context/Registry/Pipeline + 基础策略 + 占位高级策略`,默认走最低级, 高级留扩展点;新增高级策略三步(写策略对象 + register + 单测)零侵入。 ### 1.2 管线 / 中间件(Pipeline) 把一个复杂处理拆成**有序的小步骤**,每步只做一件事、可独立增删。 ``` 输入 ──▶ [校验] ──▶ [规则A] ──▶ [规则B] ──▶ [收敛] ──▶ 输出 每一节点单一职责,可插拔、可测试 ``` - **适用**:决策流水线、校验链、结算的多阶段计分(比精 → 冲关 → 霸王 → 零和)。 - **要点**:节点间用**明确的数据结构**传递,不靠隐式全局;任一节点可单测。 ### 1.3 工厂(Factory) 把"根据类型创建/选择实现"的分支收敛到一处,调用方只要"我要一个 X",不关心怎么造。 - **适用**:胡牌检测按牌型分派、可用操作枚举、不同房型的配置构建。 - **要点**:工厂是**唯一**的创建入口,避免 `if(type==...)` 散落各处(那是并行逻辑的温床)。 - **例**:胡牌检测工厂——检测逻辑只此一份,各处调用它,不自行手搓顺子/搭子判定。 ### 1.4 事件总线(EventBus)— 前端解耦,谨慎用 用发布/订阅让"状态变化"与"表现响应"解耦:处理器改完数据 `emit` 事件,表现层订阅刷新。 - **适用**:一次数据变化要驱动多个互不相关的表现(动画 + 音效 + 计分板)。 - **克制**: - **不要**用事件做"隐式流程控制"——关键流程的因果应显式可追踪,别埋进一堆事件里。 - 事件是**通知**,不是**命令**;订阅方不该反向决定发布方的流程。 - 能直接函数调用讲清的因果,就别为"解耦"硬拆成事件。 ### 1.5 统一访问层(Facade over data) 对"读权威数据"提供一个**统一入口**(如 DataAccessHelper 之类),下游都走它读,不各自摸索 数据结构。好处:数据结构演化时只改访问层一处;配合"缺失显式失败",把校验集中。 --- ## 2. 配置优先于硬编码 目标:**让"会变的东西"从代码逻辑里分离出来,用数据驱动**。改规则变成改配置,而非改逻辑 + 重测。 ### 2.1 什么必须外提(满足任一即提取) | 判据 | 说明 | 典型 | |------|------|------| | **会变** | 规则调整时可能改动 | 分数值、阈值、倍率、局数、超时 | | **复用** | 超过一个模块用到同一值 | 跨模块共享的 key、类型标识 | | **无语义** | 裸值无法自解释业务含义 | 魔法数字、状态字符串 | 按语义归入分层的常量文件(**分数/计分类、规则配置类、类型枚举类**……),不要一个巨型常量堆。 ### 2.2 什么不必外提(避免过度设计) - 单函数内一次性的临时值(循环初值 `0`、空数组 `[]`)。 - 框架约定的固定串(模块加载路径等)。 - 自解释的布尔开关、纯展示标点文字。 > 三问法:**会变吗?复用吗?有语义需要命名吗?** 三个都"否",就地内联,别为提取而提取。 ### 2.3 规则驱动:把玩法开关变成数据 复杂玩法的差异(人数/局数/扣卡方式/各种开关)应编码成**一份配置**,在入口处**解析一次**, 之后全流程**只读消费**: ``` 房型/配置编码 ──(解析器,解析一次)──▶ 规则对象(结构化、只读) │ 各模块按需读取规则对象的字段,不再各自解析原始编码、不再散落 if ``` - **解析只此一处**:避免"多处各自解析同一编码"产生的不一致(这是 SSOT 在配置上的体现)。 - **规则对象只读**:下游不回写、不推断缺省;缺字段是**配置或解析的 bug**,应显式暴露。 - **新增一个玩法开关** = 编码加一位 + 解析器认它 + 消费点读它,**不改无关逻辑**。 ### 2.4 配置注入优于全局魔法值 模块需要的配置**从入口传入 / 从权威配置对象读取**,而不是在模块内部埋一堆魔法值或直接摸全局。 这让模块**可测试**(注入不同配置跑用例)、**可复用**(换配置即换行为)。 --- ## 3. 避免过度设计(同等重要的红线) 可扩展性有成本:抽象层、间接跳转、认知负担。**过度设计和硬编码一样有害**。 ### 3.1 什么时候**不要**加抽象 - **只有一种实现、且看不到第二种的现实需求** → 不要为"将来也许"预留策略/工厂。先写直接实现。 - **YAGNI**:没有当前需求的扩展点就是负债;等第二个变体真的出现,再**重构**成模式(那时你更懂共性)。 - **一次性逻辑** → 不要包装成"通用框架"。通用性从**重复中提炼**,不是凭空设计。 ### 3.2 判断"值不值得抽象" | 加抽象 | 别加抽象 | |--------|----------| | 已有 ≥2 个真实变体,且还会增加 | 只有 1 个实现,扩展是想象的 | | 变体频繁新增(规则/策略/牌型) | 逻辑稳定,多年不变 | | 核心被扩展反复改动、回归频发 | 改动集中、影响面小 | | 抽象后核心显著变简单、变稳定 | 抽象后跳转变多、更难读 | ### 3.3 成熟的判据:三次法则 + 就近演进 - **三次法则**:同样的东西第 3 次出现时再抽象;第 1、2 次容忍重复。 - **就近演进**:先把直接实现写对、写清;当扩展点**自然浮现**(真来了第二个变体), 再**收敛**成注册表/策略——此时抽象是"被现实拉出来的",最贴合,也最不会过度。 > 一句话:**扩展性是留给"已知会变"的地方的;对"稳定不变"的地方,简单直接才是最好的设计。** --- ## 4. 本篇小结 - 扩展四件套:**注册表+策略、管线、工厂、事件总线(谨慎)**,外加**统一访问层**;够用即止。 - 配置化:会变/复用/无语义→外提;玩法差异→规则对象解析一次、只读消费;配置注入优于全局魔法值。 - 反过度设计:**YAGNI + 三次法则 + 就近演进**;只有一种实现就别造框架,抽象从重复中提炼。 下一篇 [03-数据权威·错误处理·演进](./03-数据权威·错误处理·演进.md):把"正确性"的地基——数据权威、错误处理、演进纪律——讲透。