关于 implementation-functional-patterns
面向 TypeScript 的参考技能,帮助选择函数式实现来替代基于类的 GoF 模式。它将 22 种模式映射到函数、标记联合、流和包装函数等形式,并说明放置规则与性能取舍。
主要功能
- 01将 GoF 模式映射为函数式方案
- 02涵盖工厂、闭包与高阶函数
- 03说明管道、流与包装函数
- 04用标记联合处理状态与访问者模式
- 05提供放置规则和性能取舍
适用场景
- 01将基于类的模式重构为 TypeScript 函数
- 02选择状态或组合数据的表示方式
- 03审查流链和累积循环
- 04检查 TSX 与钩子中的 Lambda 放置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pproenca/ dot-skills --skill implementation-functional-patterns安装后在 Claude Code 中输入/implementation-functional-patterns确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonreferences/_sections.mdcache-weakmap-over-flyweight.mdclosure-as-command.mdcreate-factory-function-over-factory-classes.mdcreate-fluent-immutable-builder.mdcreate-module-scope-over-singleton.mdhof-lambda-as-strategy.mdmatch-tagged-union-over-state-visitor-composite.mdpipe-compose-over-decorator.mdpipe-pipeline-over-chain-of-responsibility.mdplace-module-scope-pure-transformers.mdsignal-event-emitter-over-mediator-and-observer.mdstream-flatmap-over-nested-loops.mdstream-lazy-iteration-for-large-or-infinite.mdstream-prefer-single-pass-over-chained-passes.mdstream-reduce-over-imperative-accumulation.mdwrap-function-over-adapter-and-facade.mdwrap-proxy-native-or-hof.md详细介绍
TypeScript 函数式模式
这份参考指南介绍了在惯用 TypeScript 中取代四人帮目录的函数式形式。它与 implementation-design-patterns 配套:如果答案是类,请阅读那份指南;如果答案是函数、带标签联合类型或小型数据结构,请阅读这份指南。
TypeScript 拥有一等函数、可辨识联合类型、结构化类型,以及几乎无需额外语法即可使用的闭包。这意味着,GoF 的 22 种模式——在目录中以类层次结构的形式编写,因为源材料面向 Java/C#——在实际 TS 代码中会归并为少得多的函数式形式。若干模式有相同的函数式解法:带标签联合类型可涵盖 State、Visitor 和 Composite;工厂函数可涵盖 Factory Method、Abstract Factory、Prototype 和 Memento;事件发射器可涵盖 Mediator 和 Observer;包装函数可涵盖 Adapter 和 Facade。本指南将说明这些归并形式、它们所隐含的代码放置规则,以及性能权衡。
适用场景
- 重构工厂类层次结构、返回一系列产品的
AbstractFactory,或具有clone()/ Memento 保存与恢复功能的类 - 重构具有
private constructor+static getInstance的 Singleton 类 - 重构用于配置对象的可变 Builder 类,尤其是包含许多可选字段的类
- 重构变化仅在单个方法上的 Strategy / Template Method / Bridge 类层次结构
- 替换 Chain of Responsibility 类链或 Decorator 包装类栈
- 使用流方法、生成器或惰性迭代器辅助函数,替换自定义 Iterator 类
- 使用存储在队列中的闭包替换 Command 类(不需要撤销或序列化时)
- 替换仅用于转发调用或隐藏子系统编排的 Adapter / Facade 类
- 使用原生 JS
Proxy(用于透明拦截)或 HOF 包装器(用于选择性包装)替换 Proxy 类 - 使用
Map/WeakMap缓存 + 工厂函数替换 Flyweight 工厂类 - 使用可辨识联合类型 + 穷尽式
match函数替换 State / Visitor / Composite 类层次结构 - 使用事件发射器或响应式信号替换 Mediator / Observer 类
- 检查 TSX 中内联在 JSX、Hook 依赖项或经
memo处理的子组件属性中的 lambda——放置位置决定其标识 - 识别可用
reduce、Object.groupBy或flatMap更清晰地表达的命令式for循环及其可变累加器
规则类别
| # | 类别 | 影响 | 规则 | 主题 |
|---|---|---|---|---|
| 1 | 创建型替代方案(create) | HIGH | 3 | 工厂函数、模块作用域单例、流畅的不可变构建器 |
| 2 | 高阶函数(hof) | HIGH | 1 | 传递函数而非类 |
| 3 | 管道与组合(pipe) | HIGH | 2 | 组合小型函数:pipe(数据流)、compose(包装器分层) |
| 4 | 流方法(stream) | HIGH | 4 | map/filter/flatMap/reduce、惰性迭代、单次遍历链 |
| 5 | 包装器(wrap) | HIGH | 2 | 用于 Adapter/Facade 的包装函数;用于 Proxy 的原生 Proxy 或 HOF |
| 6 | 缓存与共享(cache) | HIGH | 1 | 用 Map/WeakMap + 工厂函数替代 Flyweight 类 |
| 7 | 模式匹配(match) | HIGH | 1 | 用于 State/Visitor/Composite 的可辨识联合类型 + 穷尽式匹配 |
| 8 | 信号与事件发射器(signal) | HIGH | 1 | 用于 Mediator/Observer 的事件发射器 / 信号 |
| 9 | 放置位置与标识(place) | HIGH | 1 | lambda 所在的位置会控制行为 |
| 10 | 作为数据的闭包(closure) | MEDIUM | 1 | 携带自身状态的函数 |
10 个类别共 17 条规则,涵盖全部 22 种 GoF 模式(部分模式共享同一种函数式解法——请参阅下方的 GoF → 函数式映射)。
GoF → 函数式映射
每种 GoF 模式在符合惯用 TS 风格的代码中的函数式实现方式完整映射。查阅此表可查找特定模式的规则;查阅上方的类别可按函数式技术查找规则。
如何使用
- 查找模式。 如果你知道要采用哪种 GoF 模式,可在上方的 GoF → 函数式映射 中查找并阅读链接的规则。如果你只知道问题症状(带累加器的循环、返回类的类、三个 setter),下方的快速参考按函数式技术对规则进行了归类。
- 阅读“何时不应采用”。 每条规则都会列出类形式仍然更适用的特定条件。这项技能是父技能的补充,而非否定——如果序列化、运行时内省、带类型的模式间关系、横切状态、框架集成或生命周期所有权有此要求,就保留类。
- 检查 TSX 中的身份假设。 如果代码位于 TSX 文件中或在 Hook 内运行,也请阅读
place-*规则——位置决定了memo、useEffect和 React Compiler 能否发挥作用。 - 注意性能。 每条规则都有
### Performance trade-offs部分,用数据说明时间、内存和分配成本。大多数函数式形式的性能与类形式相当;少数形式(链式流、flatMap)存在实际的常数级开销,在热点路径中可能很重要。
快速参考
1. 创建型替代方案
create-factory-function-over-factory-classes— 用返回带标签对象的函数替代 Factory 类层次结构。涵盖 Factory Method、Abstract Factory、Prototype 和 Memento。"我会写new FooFactory().create(),但返回带标签对象的函数更简洁,也能进行 Tree Shaking。" — HIGHcreate-module-scope-over-singleton— 使用export const x = …或延迟求值的??=,替代class X { private static instance; getInstance() }。ES 模块本身就是单例;在 TS 中使用类形式有悖惯例。"我需要这种对象恰好只有一个——配置、DB 客户端、日志记录器。" — HIGHcreate-fluent-immutable-builder— 简单场景使用对象字面量 +Partial<T>;需要跟踪类型状态的 DSL 则使用流畅的不可变构建器(每个方法都返回一个新的构建器)。"我的构造函数有 10 个参数/我的 Builder 类有 8 个 setter。" — HIGH
2. 高阶函数
hof-lambda-as-strategy— 传入比较器/谓词/转换器 Lambda,而不是定义 Strategy 类。也涵盖 Template Method(带步骤回调的 HOF)和 Bridge(由实现参数化的 HOF)。"我的 Strategy 接口只有一个方法。" — HIGH
3. 管道与组合
pipe-pipeline-over-chain-of-responsibility— 使用pipe(validate, authorize, parse)(req)或对处理器数组进行折叠,而不是创建Handler类的链表。"我的 CoR 链中的处理器各自执行一种转换,并传递结果。" — HIGHpipe-compose-over-decorator— 使用compose(withLogging, withCache, withAuth)(handler)——每个包装器都是(handler) => handler,而不是Decorator类。从右到左的顺序读起来像自顶向下的类堆栈。"我想为这个处理器添加日志记录 + 缓存 + 身份验证。" — HIGH
4. 流方法
stream-flatmap-over-nested-loops— 使用.flatMap执行一对多转换,而不是for+push或map().reduce(concat)。“对每个用户,展开其所有订单,然后收集起来。” — HIGHstream-reduce-over-imperative-accumulation— 使用reduce/Object.groupBy/Map.groupBy,而不是let acc = …; for (…) acc[…] = …。这是实际 TypeScript 中最常见的函数式模式:求和、计数、索引、直方图。“我正在循环中累加总数 / 构建索引 / 分组映射。” — HIGHstream-lazy-iteration-for-large-or-infinite— 只需要结果的前缀时,使用生成器或 TC39 Iterator 辅助方法(Iterator.from(arr).filter(p).take(10).toArray())。复杂度为 O(matched-needed),而不是 O(n)。“从庞大或无界的数据源中取出前 N 个匹配项。” — HIGHstream-prefer-single-pass-over-chained-passes— 输入量大或链式操作位于热点路径时,将.filter().map().filter()合并为一次reduce或for-of。三次遍历变为一次;峰值内存减半;实测热点路径快 2–5 倍。“这条链会在每次请求 / 渲染时,对数千个项目运行。” — HIGH
5. 包装器
wrap-function-over-adapter-and-facade— 使用包装函数将一个接口转换为另一个接口(适配器),或隐藏子系统的编排过程(外观)。“我会写一个class XAdapter implements Y,其中每个方法都只是单行转发。” — HIGHwrap-proxy-native-or-hof— 使用原生Proxy透明拦截动态键,或使用高阶函数包装器有选择地包装方法级操作。“我想要延迟加载 / 访问控制 / 拦截功能,但不想写一个 Proxy 类。” — HIGH
6. 缓存与共享
cache-weakmap-over-flyweight— 使用WeakMap/Map缓存 + 工厂函数,而不是享元工厂类。当键超出作用域时,WeakMap会自动清理。“我正在分配数百万个共享状态的相似对象。” — HIGH
7. 模式匹配
match-tagged-union-over-state-visitor-composite— 使用可辨识联合 + 对标签执行switch+assertNever,涵盖状态(每种状态一个类)、访问者(双重分派)和组合(叶节点 / 分支)。这可能是 TypeScript 中收益最高的单一函数式模式。“我的类有一个针对kind的超大 switch / 我有 N 个状态类 / 我会为 AST 编写一个访问者。” — HIGH
8. 信号与事件发射器
signal-event-emitter-over-mediator-and-observer— 使用事件发射器(mitt、EventEmitter)或响应式信号(Solid、Preact signals、Zustand),而不是 Subject / Observer 类或中央 Mediator 类。“许多对象需要在某个对象变化时做出响应 / 表单字段需要协同工作 / 需要跨组件事件。” — HIGH
9. 放置位置与标识
place-module-scope-pure-transformers— 将纯转换器 lambda 放在模块作用域(标识稳定、可复用、可进行摇树优化)。仅当需要捕获某些内容时,才将它们嵌套在函数中。“这个(s) => s.toLowerCase()不需要放在组件主体中。” — HIGH
10. 将闭包用作数据
closure-as-command— 在队列 / 历史记录 / 回调列表中存储() => void闭包,而不是带有execute()的 Command 类。“我需要一个延迟操作队列,而且永远不需要检查或序列化这些操作。” — MEDIUM
如何选择:类还是函数
当至少满足以下一项时,类形式(参见 implementation-design-patterns)所带来的额外开销才值得:
- 序列化 — 必须在进程重启后继续存在或通过网络传输的命令或备忘录
- 运行时注册表 / 自省 — 系统会枚举已知策略、在选择器中显示这些策略,或附加元数据
- 模式间的类型化关系 — 遍历 AST 的 Visitor,其中节点类型会相互引用;状态会相互引用的状态机;具有类型化角色的 Mediator
- 横切状态 — “变化项”除了单次调用外,还携带自身的配置、生命周期或不变量
instanceof的稳定标识 — 对一组有限的具名类进行穷尽匹配(较少见;判别联合通常更合适)- 生命周期所有权 — 对象拥有某种资源(连接、文件句柄、可释放对象),且需要集成
using/Symbol.dispose - 框架集成 — ORM、DI 容器、基于装饰器的库,以及基于类的 RxJS 服务都需要类
否则,默认使用函数(或带标签的联合,或数据结构)。类只是给值套上一层繁文缛节,并无实际收益。