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