implementation-functional-patterns について
クラスベースの GoF パターンに代わる関数型の形を選ぶための TypeScript リファレンススキルです。22 のパターンを関数、タグ付きユニオン、ストリームなどに対応づけ、配置ルールと性能上のトレードオフも説明します。
主な機能
- 01GoF パターンを関数型の形に対応づけ
- 02ファクトリー、クロージャ、高階関数を解説
- 03パイプライン、ストリーム、ラッパーを説明
- 04タグ付きユニオンで状態や Visitor を表現
- 05配置ルールと性能上のトレードオフを提示
活用シーン
- 01クラスベースのパターンを TypeScript の関数にリファクタリング
- 02状態や複合データの表現方法を選定
- 03ストリームチェーンや累積ループをレビュー
- 04TSX やフック内のラムダ配置を確認
インストール
コマンドをコピーしてターミナルで実行します。
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 で GoF のカタログに取って代わる関数型の構造についての実装リファレンスです。implementation-design-patterns の姉妹版です。答えがクラスの場合はそちらを、関数、タグ付きユニオン、小規模なデータ構造の場合はこのドキュメントを参照してください。
TypeScript には、第一級関数、判別可能ユニオン、構造的型付けがあり、クロージャの記述に余計な手間がかかりません。そのため、Java/C# を対象とする原典に合わせてカタログではクラス階層として記述されている GoF パターン 22 個は、実際の TS コードでははるかに少ない関数型の構造に集約されます。複数のパターンが同じ関数型の解決策を共有します。タグ付きユニオンは State、Visitor、Composite を、ファクトリー関数は Factory Method、Abstract Factory、Prototype、Memento を、イベントエミッターは Mediator、Observer を、ラッパー関数は Adapter、Facade をそれぞれカバーします。このスキルでは、こうした集約と、それに伴う配置ルールやパフォーマンス上のトレードオフについて説明します。
適用する場面
- Factory クラス階層、製品ファミリーを返す
AbstractFactory、またはclone()/Memento による保存・復元を備えたクラスをリファクタリングする場合 private constructorとstatic getInstanceを備えた Singleton クラスをリファクタリングする場合- 多数のオプションフィールドを持つ設定オブジェクト用の可変 Builder クラスをリファクタリングする場合
- 変化するのが単一のメソッドである Strategy/Template Method/Bridge クラス階層をリファクタリングする場合
- Chain of Responsibility のクラスチェーンや Decorator のラッパークラススタックを置き換える場合
- カスタム Iterator クラスを、ストリームメソッド、ジェネレーター、遅延イテレーターのヘルパーで置き換える場合
- Command クラスを、キューに格納するクロージャで置き換える場合(Undo やシリアライズが不要な場合)
- 呼び出しの転送やサブシステムのオーケストレーションの隠蔽だけを目的とする Adapter/Facade クラスを置き換える場合
- 透過的なインターセプトにはネイティブ JS の
Proxyを、選択的なラップには HOF ラッパーを使って Proxy クラスを置き換える場合 - Flyweight ファクトリークラスを、
Map/WeakMapキャッシュとファクトリー関数で置き換える場合 - State/Visitor/Composite クラス階層を、判別可能ユニオンと網羅性を検査する
match関数で置き換える場合 - Mediator/Observer クラスを、イベントエミッターまたはリアクティブシグナルで置き換える場合
- JSX 内、フックの依存配列内、または
memo化された子のプロパティ内にラムダ式がインラインで記述された TSX をレビューする場合。配置によって同一性が決まります - 可変の累積値を使う命令型の
forループのうち、reduce、Object.groupBy、またはflatMapのほうが意図を正確に表せるものを見つける場合
ルールのカテゴリ
| # | カテゴリ | 影響度 | ルール数 | テーマ |
|---|---|---|---|---|
| 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 | Flyweight クラスではなく、Map/WeakMap とファクトリー関数を使う |
| 7 | パターンマッチング(match) | HIGH | 1 | State/Visitor/Composite 向けの判別可能ユニオンと網羅的な match |
| 8 | シグナルとイベントエミッター(signal) | HIGH | 1 | Mediator/Observer 向けのイベントエミッター/シグナル |
| 9 | 配置と同一性(place) | HIGH | 1 | ラムダ式の配置場所によって動作が決まる |
| 10 | データとしてのクロージャ(closure) | MEDIUM | 1 | 状態を持つ関数 |
10 カテゴリにわたる 17 個のルールで、GoF パターン 22 個すべてをカバーします(一部のパターンは同じ関数型の解決策を共有します。以下の GoF → 関数型マップを参照してください)。
GoF → 関数型の対応表
Gang of Four の各パターンに対する、慣用的な TS における関数型の解決策をすべてまとめています。特定のパターンに対するルールを探すにはこの表を、関数型の手法ごとのルールを探すには上記のカテゴリを参照してください。
| GoF グループ | GoF パターン | 関数型の対応策 | ルール |
|---|---|---|---|
| 生成 | Factory Method | タグ付きオブジェクトを返す関数 | create-factory-function-over-factory-classes |
| Abstract Factory | コンストラクターのレコードを返す関数 | factory-function ルールで対応 | |
| Builder | オブジェクトリテラル + Partial<T>、またはイミュータブルな fluent インターフェース | create-fluent-immutable-builder | |
| Prototype | structuredClone / スプレッド構文 / Immer produce | factory-function ルールで対応 | |
| Singleton | モジュールスコープの const、遅延 ??= メモ化 | create-module-scope-over-singleton | |
| 構造 | Adapter | 形状を変換するラッパー関数 | wrap-function-over-adapter-and-facade |
| Bridge | 実装をパラメーター化した HOF | hof-lambda-as-strategy で対応 | |
| Composite | 判別共用体 + 再帰関数 | tagged-union ルールで対応 | |
| Decorator | compose(withA, withB, withC)(target) | pipe-compose-over-decorator | |
| Facade | サブシステムを隠蔽する単一の高水準関数 | wrap-function ルールで対応 | |
| Flyweight | Map/WeakMap + ファクトリー関数 | cache-weakmap-over-flyweight | |
| Proxy | ネイティブの Proxy または HOF ラッパー | wrap-proxy-native-or-hof | |
| 振る舞い | Chain of Responsibility | pipe(fn1, fn2) / 配列の fold | pipe-pipeline-over-chain-of-responsibility |
| Command | クロージャー () => void | closure-as-command | |
| Iterator | ストリームメソッド + ジェネレーター | stream-flatmap-over-nested-loops、stream-reduce-over-imperative-accumulation、stream-lazy-iteration-for-large-or-infinite、stream-prefer-single-pass-over-chained-passes | |
| Mediator | イベントエミッター / シグナル | signal-event-emitter-over-mediator-and-observer | |
| Memento | structuredClone によるイミュータブルなスナップショット | factory-function ルールで対応 | |
| Observer | イベントエミッター / シグナル / RxJS | event-emitter ルールで対応 | |
| State | 判別共用体 + 網羅的な match | match-tagged-union-over-state-visitor-composite | |
| Strategy | ラムダ | hof-lambda-as-strategy | |
| Template Method | ステップコールバックを受け取る HOF | hof-lambda ルールで対応 | |
| Visitor | 判別共用体 + match 関数 | tagged-union ルールで対応 |
使い方
- パターンを探します。 使いたいGoFパターンがわかっている場合は、上記のGoF → 関数型マップで探し、リンク先のルールを読みます。症状(アキュムレーターを使うループ、クラスを返すクラス、3つのセッター)しかわからない場合は、以下のクイックリファレンスで関数型の手法ごとにルールをまとめています。
- 「適用すべきでない場合」を読みます。 すべてのルールに、クラス形式が依然として適している限定的な条件が記載されています。このスキルは親スキルを補完するものであり、否定するものではありません。シリアライズ、ランタイムのイントロスペクション、型付きのパターン間関係、横断的な状態、フレームワークとの統合、またはライフサイクルの所有権が必要な場合は、クラスを使い続けてください。
- TSXでの同一性に関する前提を確認します。 コードがTSXファイルにある場合、またはフック内で実行される場合は、配置によって
memo、useEffect、React Compilerが適切に機能できるかが決まるため、place-*ルールも読んでください。 - パフォーマンスに注意します。 すべてのルールに、時間、メモリ、割り当てコストを定量化した
### Performance trade-offsセクションがあります。ほとんどの関数型形式はクラス形式と同等のパフォーマンスですが、一部(連結されたストリーム、flatMap)には、ホットパスで問題になる実際の定数倍のコストがあります。
クイックリファレンス
1. 生成に関する代替手法
create-factory-function-over-factory-classes— Factoryクラス階層の代わりに、タグ付きオブジェクトを返す関数を使います。Factory Method、Abstract Factory、Prototype、Mementoを扱います。「new FooFactory().create()と書くところですが、タグ付きオブジェクトを返す関数のほうが短く、ツリーシェイクも可能です。」 — HIGHcreate-module-scope-over-singleton—class X { private static instance; getInstance() }の代わりに、export const x = …または遅延評価の??=を使います。ESモジュールはシングルトンそのものです。TSではクラス形式はアンチパターンです。「設定、DBクライアント、ロガーなど、これを1つだけ必要としています。」 — HIGHcreate-fluent-immutable-builder— シンプルなケースではオブジェクトリテラルとPartial<T>を使い、型状態を追跡するDSLでは各メソッドが新しいビルダーを返す、流暢な不変ビルダーを使います。「コンストラクターにパラメーターが10個ある/Builderクラスにセッターが8個ある。」 — HIGH
2. 高階関数
hof-lambda-as-strategy— Strategyクラスを定義する代わりに、比較関数/述語/変換関数のラムダを渡します。Template Method(ステップコールバックを使う高階関数)とBridge(実装をパラメーター化した高階関数)も扱います。「Strategyインターフェースのメソッドは1つだけです。」 — HIGH
3. パイプラインと合成
pipe-pipeline-over-chain-of-responsibility—Handlerクラスの連結リストの代わりに、pipe(validate, authorize, parse)(req)またはハンドラーの配列を畳み込む方法を使います。「Chain of Responsibilityの各ハンドラーが1つの変換だけを行い、結果を次に渡しています。」 — HIGHpipe-compose-over-decorator—Decoratorクラスではなく、各ラッパーが(handler) => handlerとなるcompose(withLogging, withCache, withAuth)(handler)を使います。右から左への順序はクラスの積み重ねと同じように上から下へ読めます。「このハンドラーにロギング+キャッシュ+認証を追加したいです。」 — HIGH
4. ストリームメソッド
stream-flatmap-over-nested-loops— 1 対多の変換には、for+pushやmap().reduce(concat)ではなく.flatMapを使います。 「ユーザーごとに、すべての注文を展開してから収集する」 — HIGHstream-reduce-over-imperative-accumulation—let acc = …; for (…) acc[…] = …ではなく、reduce/Object.groupBy/Map.groupByを使います。実際の TS で最もよく使われる関数型パターンです。合計、件数、インデックス、ヒストグラムなどに使います。 「ループで合計/インデックス/グループ化したマップを作っています」 — HIGHstream-lazy-iteration-for-large-or-infinite— 結果の先頭部分だけが必要な場合は、ジェネレーターまたは TC39 Iterator ヘルパー(Iterator.from(arr).filter(p).take(10).toArray())を使います。O(n) ではなく O(必要な一致数) になります。 「巨大な、または無制限のソースから最初の N 件の一致を取得する」 — HIGHstream-prefer-single-pass-over-chained-passes— 入力が大きい場合やチェーンが頻繁に実行される場合は、.filter().map().filter()を 1 つのreduceまたはfor-ofにまとめます。3 回の走査が 1 回になり、ピークメモリを半減し、測定済みのホットパスでは 2~5 倍高速になります。 「このチェーンは、数千件の要素に対してリクエスト/レンダリングのたびに実行されます」 — HIGH
5. ラッパー
wrap-function-over-adapter-and-facade— あるインターフェースを別のインターフェースに変換するラッパー関数(Adapter)、またはサブシステムのオーケストレーションを隠すラッパー関数(Facade)を使います。 「すべてのメソッドが 1 行の転送処理になるclass XAdapter implements Yを書こうとしています」 — HIGHwrap-proxy-native-or-hof— 動的なキーを透過的にインターセプトするにはネイティブのProxyを、メソッド単位で選択的にラップするには HOF ラッパーを使います。 「Proxy クラスを使わずに、遅延読み込み/アクセス制御/インターセプトを実現したいです」 — HIGH
6. キャッシュと共有
cache-weakmap-over-flyweight— Flyweight ファクトリークラスではなく、WeakMap/Mapキャッシュとファクトリー関数を使います。キーがスコープ外になると、WeakMapは自動的にクリーンアップされます。 「共有状態を持つ類似オブジェクトを何百万個も割り当てています」 — HIGH
7. パターンマッチング
match-tagged-union-over-state-visitor-composite— 判別可能なユニオン + タグに対するswitch+assertNeverを使います。State(状態ごとのクラス)、Visitor(ダブルディスパッチ)、Composite(Leaf/Branch)をカバーします。おそらく TS で最も効果の高い関数型パターンです。 「クラスにkindを使った巨大な switch があります/N 個の状態クラスがあります/AST に対する Visitor を書こうとしています」 — HIGH
8. シグナルとイベントエミッター
signal-event-emitter-over-mediator-and-observer— Subject/Observer クラスや中央の Mediator クラスではなく、イベントエミッター(mitt、EventEmitter)またはリアクティブシグナル(Solid、Preact signals、Zustand)を使います。 「1 つのオブジェクトが変わったときに多くのオブジェクトが反応する必要があります/フォームフィールド間の連携が必要です/コンポーネント間のイベントがあります」 — HIGH
9. 配置と同一性
place-module-scope-pure-transformers— 純粋な変換用ラムダはモジュールスコープに配置します(同一性が安定し、再利用可能で、ツリーシェイク可能です)。何かをキャプチャする場合にのみ、関数内に配置します。 「この(s) => s.toLowerCase()はコンポーネント本体に置く必要がありません」 — HIGH
10. データとしてのクロージャ
closure-as-command—execute()を持つ Command クラスではなく、キュー/履歴/コールバックリストに() => voidクロージャを格納します。 「遅延操作のキューが必要ですが、その内容を調べたりシリアライズしたりする必要はありません」 — MEDIUM
クラスと関数の選び方
クラス形式(implementation-design-patternsを参照)は、次のいずれか1つ以上に当てはまる場合に、そのオーバーヘッドに見合う価値があります。
- シリアライズ — プロセスの再起動後も保持する必要がある、または通信経路を越えて受け渡す必要があるコマンドやMemento
- 実行時レジストリ/イントロスペクション — システムが既知の戦略を列挙する、選択リストに表示する、またはメタデータを付与する
- パターン間の型付き関係 — ノード型が互いを参照するASTに対するVisitor、状態が互いを参照するステートマシン、型付きロールを持つMediator
- 横断的な状態 — 「バリエーション」が、単一の呼び出しを超える独自の設定、ライフサイクル、または不変条件を持つ
instanceofのための安定した識別性 — 名前付きクラスの有限集合に対する網羅的なマッチング(まれなケースで、通常は判別可能なユニオンが適しています)- ライフサイクルの所有 — オブジェクトがリソース(接続、ファイルハンドル、破棄可能なオブジェクト)を所有し、
using/Symbol.disposeとの連携が重要 - フレームワークとの統合 — ORM、DIコンテナ、デコレーターベースのライブラリ、RxJSのクラスベースサービスがクラスを前提としている
それ以外の場合は、関数(またはタグ付きユニオン、あるいはデータ構造)をデフォルトにしてください。クラスは、得るもののない形式的な手続きを追加して値を包み込みます。