7.7 KiB
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-数据权威·错误处理·演进:把"正确性"的地基——数据权威、错误处理、演进纪律——讲透。