共计 2606 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点分析
在微服务或插件化架构中,Skill 模块的设计常面临以下典型问题:

- 定义边界模糊 :业务规则与技能逻辑耦合,导致修改成本呈指数增长
- 上下文污染 :全局状态被多个 Skill 共享,引发难以追踪的副作用
- 复用困难 :缺乏标准接口定义,相似功能重复实现
技术实现对比
1. 面向对象继承
class BaseSkill {execute() {/* 基础实现 */}
}
// 问题:多层继承导致方法膨胀
class FireSkill extends BaseSkill {execute() {/* 火焰特效处理 */}
}
劣势 :
– 容易形成金字塔式继承结构
– 子类可能破坏父类契约
2. 组合模式
interface ISkill {execute(ctx: SkillContext): Promise<void>;
}
// 优点:功能单元可自由装配
class CompositeSkill implements ISkill {constructor(private skills: ISkill[]) {}
async execute(ctx: SkillContext) {for (const skill of this.skills) {await skill.execute(ctx);
}
}
}
优势 :
– 运行时动态调整技能组合
– 符合开闭原则
3. 函数式编程
type SkillExecutor = (ctx: SkillContext) => Effect<void>;
// 优点:无副作用、易于测试
const healSkill: SkillExecutor = (ctx) => {return ctx.player.hp += 10;};
适用场景 :
– 纯计算型技能
– 需要高阶函数组合时
核心实现方案
基于 DI 的 TypeScript 实现
// 核心类型定义
interface SkillParams {
cooldown: number;
dependencies?: string[];}
abstract class AbstractSkill {
constructor(
protected params: SkillParams,
@inject(Logger) private logger: Logger
) {}
abstract effect(ctx: SkillContext): Promise<void>;
async execute(ctx: SkillContext) {if (this.checkCooldown(ctx)) {await this.effect(ctx);
this.recordUsage(ctx);
}
}
}
单元测试示例
describe('FireSkill', () => {it('should damage target', async () => {const skill = new FireSkill({ cooldown: 5});
const mockCtx = {target: { hp: 100} };
await skill.execute(mockCtx);
expect(mockCtx.target.hp).toBeLessThan(100);
});
});
装饰器模式应用
function LoggableSkill() {return function <T extends { new (...args: any[]): {}}>(constructor: T) {
return class extends constructor {execute(ctx: SkillContext) {console.log(`[${new Date()}] Skill triggered`);
return super.execute(ctx);
}
};
};
}
@LoggableSkill()
class StealthSkill extends AbstractSkill {//...}
生产环境考量
并发状态管理
sequenceDiagram
participant Client
participant SkillService
participant Redis
Client->>SkillService: execute(skillId)
SkillService->>Redis: GET cooldown:{userId}:{skillId}
alt 冷却中
Redis-->>SkillService: 剩余时间
SkillService-->>Client: 429 Too Many Requests
else 可用
SkillService->>Redis: SETEX cooldown 30s
SkillService->>DB: 持久化记录
SkillService-->>Client: 200 OK
end
循环依赖检测
class SkillGraph {private adjacencyList = new Map<string, string[]>();
addDependency(skill: string, dep: string) {
// 添加边后检测环
if (this.hasCycle()) {throw new SkillDependencyError('Circular dependency detected');
}
}
private hasCycle(): boolean {// 实现 DFS 环检测算法}
}
避坑指南
避免过度抽象
- YAGNI 原则 :当前不需要的功能不提前抽象
- 三次法则 :重复出现三次以上的模式才考虑抽象
- 显式优于隐式 :避免魔法字符串 / 数字,使用枚举或常量
性能优化方案
// 技能预热示例
class SkillPreloader {private warmCache = new Map<string, Promise<ISkill>>();
preload(skillClass: new () => ISkill) {const promise = import(`./skills/${skillClass.name}`)
.then(module => new module.default());
this.warmCache.set(skillClass.name, promise);
}
async get(skillName: string): Promise<ISkill> {return this.warmCache.get(skillName);
}
}
思考题
如何设计支持热更新的 Skill 系统?考虑以下维度:
- 版本兼容性保障机制
- 运行时替换 class 的方案
- 内存中旧技能实例的清理策略
- 依赖项变更时的级联更新处理
欢迎在评论区分享你的设计方案。
正文完
发表至: 编程技术
近一天内
