12. Subclass Sandbox
12. Subclass Sandbox:由基类提供受保护原语,让子类在有限能力内组合行为,通过可复位因果实验和反例证据验收。
12. Subclass Sandbox
学习目标
- 能说明沙箱基类的作用
- 能解释引擎改动如何被隔离
- 能选择操作粒度与注入方式
为什么"12. Subclass Sandbox"从问题证据开始
你的游戏有几十种"超能力":火球、冰霜、治疗、传送……每种能力都要调用引擎的音频、粒子、伤害系统。最直觉的实现是让每个能力类直接调用引擎 API:AudioSystem::play("fire")、ParticleSystem::spawn("flame")。代码今天能跑,但每次引擎改 API(比如音频系统重构),几十个能力类全部要跟着改——改动波及面巨大,而且漏改一个就是运行时崩溃。
本页把"无模式基线"定义为"子类直接调用引擎 API"的代码,然后引入沙箱基类作为候选机制,验证它是否真的让"引擎改动只波及一处"。通过条件是引擎 API 变化时只需改基类一个文件——而不是每个能力类。
🔮 先预测:引擎音频 API 改名,沙箱方案要改几个文件?
来源、版本与独立重写边界
本页用作者完整在线正文核对正式标题、设计分叉和时代语境,并以作者源码仓库交叉检查结构。仓库许可证明确正文、HTML与样式为 CC BY-NC-ND 4.0,示例程序等其他文件为 MIT;因此下列中文解释、图示、交互和代码均为独立教学重写,不翻译、拼接或改写受 ND 限制的原文表达。
本章机制与术语
理解 Subclass Sandbox 需要四个核心概念:
- ↡:提供一组受保护操作(playSound/spawnParticle/damage)的基类——子类唯一能碰引擎的通道。
- ↡:继承基类、实现具体行为(activate())的能力类——只调用基类提供的操作。
- ↡:基类封装引擎调用的方法——子类调用它们,但不直接碰引擎。
- ↡:子类把多个沙箱操作组合成完整效果——行为 = 沙箱操作的编排。
四个概念共同约束"由基类提供受保护原语,让子类在有限能力内组合行为":任何结论都必须回到"引擎改动波及范围、子类对引擎的耦合度、行为组合自由度"三个可观察量。
Subclass Sandbox — 子类只会在沙箱里玩
第 1 / 4 步 · ① Sandbox 基类:提供受保护操作(playSound/spawnParticle/damageTarget)
子类能做的所有事都来自沙箱——引擎改动被隔离在基类,子类零波及。
💡 对着动画看:动画是三栏结构——左侧 Superpower 沙箱基类(protected 操作清单),中间 FireballPower 子类(override activate() 里组合 playSound/spawnParticle/damageTarget),右侧引擎。注意子类到引擎之间没有直接连线——它只认识沙箱。读"引擎改动如何隔离"时回到这张图:改引擎只动基类那一栏。
官方结构逐项深读
12. Subclass Sandbox
Subclass Sandbox 的主张:把子类对引擎的直接依赖,收进一个沙箱基类。基类提供受保护的操作方法(内部调用引擎 API),子类只调用这些方法组合行为。这样子类不认识引擎——它认识的是"能播放声音、能生成粒子、能造成伤害"这套抽象能力。引擎内部怎么实现(音频用 OpenAL 还是 DirectSound)与子类无关。
Intent
意图:解耦"行为定义"(子类)与"引擎能力"(底层 API)。子类专心定义"火球术做什么",基类专心封装"引擎能做什么"。引擎改版时,只有基类需要适配;子类代码一行不动。这也是"稳定抽象层"思想的体现:沙箱是行为与引擎之间的稳定接口。
Motivation
回到超能力系统的现实:火球术要播音效、生成粒子、对目标造成伤害。如果三个操作直接散落在 FireballPower 里调用引擎 API,那么引擎音频系统一重构,FireballPower、IcePower、TeleportPower……所有能力类都要改。动机就是消灭这种"引擎改动波及所有行为"的脆弱性——把变化收敛到基类。
The Pattern
模式结构:抽象基类 Superpower(protected 方法 playSound/spawnParticle/damageTarget + 抽象方法 activate)→ 具体子类 FireballPower(实现 activate,调用基类方法)→ 引擎(创建能力实例并调用 activate())。子类永远不直接调用引擎 API——所有能力通过基类的受保护方法间接获得。基类是唯一认识引擎的类。
When to Use It
何时用沙箱:有一族行为类(技能、AI 状态、任务)共享同一批引擎能力,且引擎 API 频繁变动。何时不用:行为只有一两个类(没必要建抽象层)、行为需要直接操作引擎细粒度接口(沙箱太粗)。判断标准:"改动引擎会波及多少行为类"——波及面大就用沙箱收敛。
Keep in Mind
沙箱的代价:间接层(调用多一层)、基类膨胀(所有能力要的操作都堆进基类)、能力受限(子类只能做基类允许的事——这既是安全也是限制)。作者强调:沙箱是"为了解耦而增加的一层"——当引擎真的很稳定、行为真的很少时,这层就是浪费。
Sample Code
示例代码:Superpower 基类持引擎引用(构造时注入),protected 方法内部调用引擎;FireballPower::activate() 组合三个操作。核心结构:class Superpower { protected: void playSound(id); void spawnParticle(id); void damage(target, amount); }; class FireballPower : Superpower { void activate() override; };——子类看不到引擎,只看到能力。
Design Decisions
三个关键设计决策:提供什么操作(粗粒度 vs 细粒度)、方法直接提供还是通过对象提供(基类方法 vs 能力对象)、基类怎么拿状态(构造注入 vs 全局获取)。前两者决定沙箱的"边界形状",后者决定基类与引擎的耦合方式——下面三节展开。
What operations should be provided?
操作粒度:粗粒度操作(playSound(id)——一条命令封装音效系统调用,子类好写、引擎好改)与细粒度操作(openAudioChannel/loadSample/play——子类灵活但直接暴露引擎结构)。作者建议:从粗粒度起步——沙箱的意义就是让子类简单,细粒度操作会把它拉回"子类碰引擎"的老路。需要更细控制时,再向基类加粗粒度方法。
Should methods be provided directly, or through objects that contain them?
两种提供方式:直接方法(playSound(id) 是基类方法——简单直接,但所有操作挤在基类,类会膨胀)与通过能力对象(基类持有 Audio 对象,子类调 audio->play(id)——把操作分组到对象,基类瘦身,子类语义更清晰:audio.play() 明确是音频能力)。作者建议:操作多了以后用能力对象分组(audio/particle/combat 各一对象),基类只做装配。
How does the base class get the state that it needs?
基类拿引擎状态的方式:构造注入(Superpower 构造时接收引擎引用——显式、可测,但每个能力创建时都要传)与全局获取(基类内部用 Service Locator 或单例拿引擎——方便,但隐藏依赖、难测试)。作者建议:构造注入优先——子类创建时显式传入所需系统,测试时容易替换替身;全局获取是便利但耦合的退路。
See Also
- Template Method:沙箱的基类定义操作骨架、子类实现步骤——本质是模板方法的"行为组合"用法。
- Strategy:策略模式用组合替换行为,沙箱用继承提供能力——一个选行为,一个给能力。
- Component:组件模式把能力拆到组件,与沙箱的"能力收进基类"互补——超复杂实体可用组件,简单行为族用沙箱。
可迁移实现或计算骨架
initial state -> 基类封装引擎操作(protected)-> 子类组合操作实现行为 -> 引擎调用 activate()
fault injection -> 子类直接调用引擎 API
pass condition -> 引擎 API 改动只波及基类一个文件
reset -> initial state对应实现要点:基类构造注入引擎引用;protected 方法粗粒度封装引擎调用;子类只调基类方法;操作多时用能力对象分组。该骨架只保存实验合同;真实项目还要固定能力清单、注入路径与测试替身,并保留基线实现以便回退。
本章练习与节点验证矩阵
练习
问题 1:波及面对比。
操作:模拟引擎音频 API 改名,统计基线与沙箱方案各要改几个文件
问题 2:能力边界。
操作:在动画沙箱中"新增"一个"传送"能力,只用现有操作能否表达
问题 3:注入 vs 全局。
操作:分别用构造注入与 Service Locator 实现基类拿引擎,各写一个测试
术语复核
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 沙箱基类
- 受保护操作
- 组合
练习答案参考
练习判据即答案:每个练习的"验证判据"列给出了通过标准——先自己动手,再对照判据核验。
术语复核与本章回顾
掌握"12. Subclass Sandbox"意味着能从"子类直接调用引擎 API 让引擎改动波及所有行为"出发,解释沙箱基类、子类、受保护操作与组合四者的关系,再用"引擎改动波及范围、子类对引擎的耦合度、行为组合自由度"三个可观察量推翻或保留实现。若三个判据不能同时满足,本章仍未通过。
一句话回顾:引擎把"能做的事"全收进沙箱基类,子类只会组合这些能力——引擎改动被基类一堵墙挡住,行为类从此稳如泰山。
阅读导航
← 上一页:11. Bytecode · 下一页:13. Type Object → ← 上一页:11. Bytecode · 下一页:13. Type Object →