JavaScript 设计模式
设计模式是在软件设计中,针对常见问题的、经过验证的可重用解决方案。它们不是具体的代码,而是一种解决问题的思想和模板。在游戏开发中,使用正确的设计模式可以使你的代码更灵活、可维护和可扩展。
使用指南与分类
- 学习目标:先理解“意图”和“场景”,再看示例,最后自己小改动练习。
- 模式分类:
- 创建型(创建对象):单例、工厂、抽象工厂、建造者、原型
- 结构型(组织结构):适配器、桥接、组合、装饰器、外观、享元、代理
- 行为型(对象协作):责任链、命令、解释器、迭代器、中介者、备忘录、状态、策略、模板方法、观察者、访问者
- 推荐学习路径(由易到难):
- 单例 → 2) 观察者 → 3) 工厂 → 11) 外观 → 21) 策略 → 20) 状态 → 15) 命令 → 14) 责任链 → 7) 适配器 → 10) 装饰器 → 9) 组合 → 8) 桥接 → 12) 享元 → 13) 代理 → 22) 模板方法 → 18) 中介者 → 17) 迭代器 → 19) 备忘录 → 23) 访问者 → 16) 解释器 → 4) 抽象工厂 → 5) 建造者 → 6) 原型
[创建型] 1. 单例模式 (Singleton)
意图:确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
游戏开发场景:管理游戏全局状态、设置、或一个核心系统(如资源管理器、输入处理器)时非常有用,因为你只需要一个这样的管理器。
类比:学校里只有一个校长,大家找同一个人说话。
// GameSettings.js — 用闭包里的局部变量 `instance` 保存唯一实例
let instance; // 模块级私有变量,外部不可直接访问
class GameSettings {
constructor() {
// 若已存在实例,阻止再次 new,保证唯一性
if (instance) {
throw new Error("你不能创建多个 GameSettings 实例!");
}
instance = this; // 第一次构造时,把自己记下来
// 默认设置
this.volume = 80;
this.difficulty = "Normal";
}
static getInstance() {
// 提供全局访问点:永远返回同一个实例
if (!instance) {
instance = new GameSettings();
}
return instance;
}
}
// 使用示例:无论获取多少次,都是同一个对象
const settings1 = GameSettings.getInstance();
const settings2 = GameSettings.getInstance();
console.log(settings1 === settings2); // true
settings1.volume = 100; // 改变一个的属性
console.log(settings2.volume); // 100,同步反映在另一个上
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[行为型] 2. 观察者模式 (Observer)
意图:定义对象之间的一对多依赖关系。当一个对象(“主题”或“发布者”)的状态发生改变时,所有依赖于它的对象(“观察者”)都会得到通知并自动更新。
游戏开发场景:UI 更新(当玩家生命值变化时,UI 血条自动更新)、成就系统(当玩家完成特定任务时,成就系统得到通知)等。
类比:上课铃一响,所有班级同时开始上课。
// Subject (发布者):维护一个观察者列表,有变化就通知大家
class HealthSystem {
constructor() {
this.observers = []; // 存放所有观察者
this.health = 100; // 被观察的状态
}
addObserver(observer) {
this.observers.push(observer); // 订阅
}
removeObserver(observer) {
this.observers = this.observers.filter((obs) => obs !== observer); // 退订
}
notifyObservers() {
// 广播:逐个调用观察者的 update 接口
this.observers.forEach((observer) => observer.update(this.health));
}
takeDamage(amount) {
// 状态变化:扣血并通知
this.health -= amount;
console.log(`玩家受到 ${amount} 点伤害。`);
this.notifyObservers();
}
}
// Observer (观察者):实现 update 接口,接收发布者的最新状态
class HealthBarUI {
update(health) {
console.log(`[UI] 更新血条显示: ${health}`);
}
}
class AchievementSystem {
constructor() {
this.achievementUnlocked = false;
}
update(health) {
if (health < 20 && !this.achievementUnlocked) {
console.log("[成就] 解锁成就:命悬一线!");
this.achievementUnlocked = true;
}
}
}
// 使用:把 UI 和 成就系统 都订阅到生命系统上
const healthSystem = new HealthSystem();
const healthBar = new HealthBarUI();
const achievements = new AchievementSystem();
healthSystem.addObserver(healthBar);
healthSystem.addObserver(achievements);
healthSystem.takeDamage(30); // [UI] 更新血条显示: 70
healthSystem.takeDamage(60); // [UI] 更新血条显示: 10, [成就] 解锁成就:命悬一线!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
[创建型] 3. 工厂模式 (Factory Method)
意图:定义一个用于创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
游戏开发场景:创建不同类型的敌人、物品或特效,而无需在主代码中指定具体的类。
类比:点餐只说“来一份汉堡/薯条”,后厨按类型做好给你。
// 敌人与其子类:把差异封装在类里
class Enemy {
constructor(name, health) {
this.name = name; // 敌人名称
this.health = health; // 初始生命
}
}
class Goblin extends Enemy {
constructor() {
super("哥布林", 50); // 具体配置在子类中体现
}
}
class Orc extends Enemy {
constructor() {
super("兽人", 150);
}
}
// 敌人创建工厂:只关心“类型”,不关心“怎么创建”
class EnemyFactory {
create(type) {
switch (type) {
case "goblin":
return new Goblin();
case "orc":
return new Orc();
default:
throw new Error("未知的敌人类型");
}
}
}
// 使用:高层代码无需知道具体类名,只传入“类型”
const factory = new EnemyFactory();
const enemies = [];
enemies.push(factory.create("goblin"));
enemies.push(factory.create("orc"));
console.log(enemies); // [Goblin, Orc]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
[创建型] 4. 抽象工厂模式 (Abstract Factory)
意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定具体类。
游戏场景:不同关卡主题下的“成套”对象(敌人、道具、背景)。
类比:主题礼盒,一打开就是同风格的帽子+气球+贴纸。
// 抽象工厂
class ThemeFactory {
createEnemy() {}
createItem() {}
}
class ForestThemeFactory extends ThemeFactory {
createEnemy() {
return { type: "ForestWolf" };
}
createItem() {
return { type: "HealingHerb" };
}
}
class DesertThemeFactory extends ThemeFactory {
createEnemy() {
return { type: "SandScorpion" };
}
createItem() {
return { type: "CoolWater" };
}
}
function setupLevel(factory) {
const enemy = factory.createEnemy();
const item = factory.createItem();
console.log(enemy, item);
}
setupLevel(new ForestThemeFactory());
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[创建型] 5. 建造者模式 (Builder)
意图:将复杂对象的构建与其表示分离,使同样的构建过程可以创建不同的表示。
游戏场景:逐步构建复杂角色或关卡配置。
类比:搭乐高,一块块拼,最后变成城堡。
class CharacterBuilder {
constructor() {
this.c = {};
}
setName(name) {
this.c.name = name;
return this;
}
setHP(hp) {
this.c.hp = hp;
return this;
}
setWeapon(w) {
this.c.weapon = w;
return this;
}
build() {
return this.c;
}
}
const hero = new CharacterBuilder()
.setName("Hero")
.setHP(100)
.setWeapon("Sword")
.build();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[创建型] 6. 原型模式 (Prototype)
意图:通过复制已有对象(原型)来创建新对象。
游戏场景:大量相似对象的高效创建(子弹、粒子)。
类比:用复印机复印一张,再在复印件上涂色。
const bulletProto = {
speed: 10,
damage: 5,
clone() {
return { ...this };
},
};
const b1 = bulletProto.clone();
2
3
4
5
6
7
8
[结构型] 7. 适配器模式 (Adapter)
意图:将一个类的接口转换成客户期望的另一个接口。
游戏场景:适配第三方输入库到你的统一输入接口。
类比:插头转换器,把三孔变成两孔,也能用。
class GamepadAdapter {
constructor(third) {
this.third = third;
}
getMoveAxis() {
return { x: this.third.axisX(), y: this.third.axisY() };
}
}
2
3
4
5
6
7
8
[结构型] 8. 桥接模式 (Bridge)
意图:将抽象部分与实现部分分离,使它们可以独立变化。
游戏场景:武器(抽象)与伤害算法(实现)分离。
类比:遥控车和电池分开,换电池不用换遥控车。
class DamageCalc {
calc(base) {
return base;
}
}
class CritDamage extends DamageCalc {
calc(base) {
return base * 2;
}
}
class Weapon {
constructor(calc) {
this.calc = calc;
}
hit() {
return this.calc.calc(10);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[结构型] 9. 组合模式 (Composite)
意图:将对象组合成树形结构以表示“部分-整体”。
游戏场景:UI 节点树、场景层级。
类比:文件夹里可以装文件,也能装小文件夹。
class Node {
constructor(name) {
this.name = name;
this.children = [];
}
add(n) {
this.children.push(n);
}
}
2
3
4
5
6
7
8
9
[结构型] 10. 装饰器模式 (Decorator)
意图:在不改变原类的情况下,动态地给对象添加职责。
游戏场景:给角色临时增加“护盾”“加速”等效果。
类比:冰淇淋上加巧克力/坚果,口味更丰富。
function withShield(character) {
return {
...character,
damage(d) {
character.hp -= Math.max(0, d - 5);
},
};
}
2
3
4
5
6
7
8
[结构型] 11. 外观模式 (Facade)
意图:为复杂子系统提供一个统一的高层接口。
游戏场景:一键初始化资源、声音、网络等。
类比:总服务台,一个窗口帮你联系所有部门。
class GameFacade {
start() {
this.loadAssets();
this.initAudio();
this.connect();
}
loadAssets() {
/* ... */
}
initAudio() {
/* ... */
}
connect() {
/* ... */
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[结构型] 12. 享元模式 (Flyweight)
意图:共享细粒度对象,节省内存。
游戏场景:地图瓦片、子弹外观复用。
类比:共享单车,很多人共用同一批车。
const SpriteCache = new Map();
function getSprite(key) {
if (!SpriteCache.has(key)) SpriteCache.set(key, { image: new Image() });
return SpriteCache.get(key);
}
2
3
4
5
[结构型] 13. 代理模式 (Proxy)
意图:为其他对象提供一种代理以控制对这个对象的访问。
游戏场景:远程服务(RPC)、延迟加载资源。
类比:请代购帮你买东西,他先去买再给你。
function lazyTexture(path) {
let tex = null;
return {
get() {
if (!tex) {
tex = { path };
}
return tex;
},
};
}
2
3
4
5
6
7
8
9
10
11
[行为型] 14. 责任链模式 (Chain of Responsibility)
意图:将请求沿链传递,直到被处理。
游戏场景:输入事件层层传递(UI → 玩家 → 世界)。
类比:传话游戏,一直传到能回答的人为止。
const h1 = (next) => (e) => e.type === "UI" ? console.log("UI") : next(e);
const h2 = (next) => (e) =>
e.type === "Player" ? console.log("Player") : next(e);
const chain = h1(h2((e) => console.log("World")));
2
3
4
[行为型] 15. 命令模式 (Command)
意图:将请求封装为对象,支持撤销/重做、队列等。
游戏场景:编辑器操作、宏录制。
类比:把任务写在便条纸上,做了可以撤销重做。
class MoveCmd {
constructor(player, dx, dy) {
this.p = player;
this.dx = dx;
this.dy = dy;
}
exec() {
this.p.x += this.dx;
this.p.y += this.dy;
}
undo() {
this.p.x -= this.dx;
this.p.y -= this.dy;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[行为型] 16. 解释器模式 (Interpreter)
意图:为语言定义文法,并用解释器解释句子。
游戏场景:简单脚本/对话指令解析。
类比:查字典,把“向前走”翻成实际动作。
function interpret(cmd) {
if (cmd === "JUMP") return "jump";
if (cmd.startsWith("SAY ")) return cmd.slice(4);
}
2
3
4
[行为型] 17. 迭代器模式 (Iterator)
意图:提供一种顺序访问聚合对象元素的方法,而不暴露其内部表示。
游戏场景:统一遍历实体集合。
类比:排队一个个过闸门,轮到谁谁先走。
const collection = {
items: [1, 2, 3],
[Symbol.iterator]() {
let i = 0;
return {
next: () => ({ value: this.items[i++], done: i > this.items.length }),
};
},
};
2
3
4
5
6
7
8
9
[行为型] 18. 中介者模式 (Mediator)
意图:用一个中介对象封装一系列对象的交互。
游戏场景:UI 控件之间、模块间通信。
类比:班长负责转达同学之间的话,大家不需要互相大喊。
class Mediator {
publish(e, p) {
(this[e] || []).forEach((f) => f(p));
}
subscribe(e, f) {
(this[e] = this[e] || []).push(f);
}
}
2
3
4
5
6
7
8
[行为型] 19. 备忘录模式 (Memento)
意图:在不破坏封装的前提下,捕获对象内部状态,以便之后恢复。
游戏场景:存档/撤销。
类比:游戏里的存档点,回去就像时光倒流。
class Caretaker {
constructor() {
this.stack = [];
}
save(s) {
this.stack.push(JSON.stringify(s));
}
load() {
return JSON.parse(this.stack.pop());
}
}
2
3
4
5
6
7
8
9
10
11
[行为型] 20. 状态模式 (State)
意图:对象在内部状态改变时改变其行为。
游戏场景:角色“站立/奔跑/攻击”状态切换。
类比:红绿灯变颜色,路人的行动跟着改变。
class Player {
constructor() {
this.state = new Idle(this);
}
setState(s) {
this.state = s;
}
update() {
this.state.handle();
}
}
class Idle {
constructor(p) {
this.p = p;
}
handle() {
/* ... */
}
}
class Run {
constructor(p) {
this.p = p;
}
handle() {
/* ... */
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[行为型] 21. 策略模式 (Strategy)
意图:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。
游戏场景:不同寻路/AI 决策。
类比:走迷宫可以选“左手法”或“看地图”,方法能替换。
const AStar = {
path(a, b) {
/* ... */
},
};
const Dijkstra = {
path(a, b) {
/* ... */
},
};
function Navigator(strategy) {
this.s = strategy;
this.find = (a, b) => this.s.path(a, b);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
[行为型] 22. 模板方法模式 (Template Method)
意图:定义算法骨架,将一些步骤延迟到子类。
游戏场景:关卡通用流程(加载 → 开始 → 结束),细节由子类决定。
类比:做蛋糕步骤固定(搅拌 → 烤 → 装饰),口味自己选。
class LevelTemplate {
start() {
this.load();
this.play();
this.end();
}
load() {
/* hook */
}
play() {
/* hook */
}
end() {
/* hook */
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[行为型] 23. 访问者模式 (Visitor)
意图:在不改变数据结构的前提下,定义作用于其元素的新操作。
游戏场景:对场景树执行“统计”“导出”等操作。
类比:游园时给每棵树贴标签,统计高度与数量。
class Visitor {
visitNode(n) {
/* ... */
}
}
function traverse(node, v) {
v.visitNode(node);
node.children?.forEach((c) => traverse(c, v));
}
2
3
4
5
6
7
8
9
学习和应用设计模式可以帮助你构建更强大、更易于维护的游戏逻辑。建议:
- 简读“意图”,先理解适用场景。
- 复制示例,改成你的游戏对象名。
- 从最常用的开始:单例、观察者、策略、命令、状态、外观。