l

o

a

d

i

n

g

.

.

.

GoF以外のプログラミング・デザインパターン #29 Pipeline / Chain of Responsibility(拡張版)

2025/12/20

プログラミング 学習

t f B! P L
eyecatch Pipeline / Chain of Responsibility(拡張版)というパターンは、一連の処理を、直列につないだ“流れ”として扱うデザインパターンです。 各処理(ハンドラ)が「入力を受け → 加工 → 次へ渡す」を行い、Chain of Responsibility の「必要なハンドラだけ処理する」という思想 + Pipeline の「すべてを順番に処理する」構造を合わせた拡張イメージです。 条件分岐だらけの処理を避け、追加・差し替え・テストがしやすい柔軟な構造を作れるのが特徴ですね。

よく使われる場面

・Webアプリのリクエスト処理(LaravelのMiddlewareなど) ・データ加工のトランスフォーマーチェーン ・入力バリデーションの多段チェック ・ログ処理 → 認証 → 権限 → ビジネスロジック…の順番処理 ・ChatGPT API などでの前処理 → プロンプト整形 → 後処理のパイプライン ・リクエスト処理(API Gateway / Middleware) ・画像やデータの変換処理 ・ETL パイプライン ・バリデーションや正規化ロジック ・イベントストリーム(Kafka など) ・ゲームロジックの行動処理ステップ ・Webアプリのミドルウェア構造(Express, Koa)

このデザインパターンの特徴

・ハンドラを配列やリストで持ち、実行順序を動的に変更できる。 ・途中で“次に進むかどうか”を制御できる。 ・ステート(コンテキスト)を共有しながら進められる。 ・非同期処理にも強い。(特にJavaScript)

構造図

[ Input ] | v +---------+ +---------+ +---------+ | Step 1 | ---> | Step 2 | ---> | Step 3 | +---------+ +---------+ +---------+ | | +---------- 完了 or 中断 --------+

サンプルコード

class Pipeline { constructor() { this.steps = []; } use(fn) { this.steps.push(fn); return this; // チェーン構築しやすい } async run(context) { let index = 0; const next = async () => { if (index >= this.steps.length) return; const step = this.steps[index++]; await step(context, next); }; await next(); return context; } } // ---- 使用例 ---- // Step1 const addUserId = async (ctx, next) => { ctx.userId = 123; await next(); }; // Step2(途中終了する例) const checkAuth = async (ctx, next) => { if (!ctx.userId) return; // パイプライン中断 ctx.auth = true; await next(); }; // Step3 const loadData = async (ctx, next) => { ctx.data = ["apple", "banana"]; await next(); }; // 実行 const pipeline = new Pipeline() .use(addUserId) .use(checkAuth) .use(loadData); pipeline.run({}).then(console.log);

ポイント解説

・大規模化しても読みやすい。 ・if/else だらけのゴリゴリ処理が消える ・新しい処理を1クラス/1関数として追加するだけ ・テストしやすい(個々のステップを単体テスト) ・処理順を柔軟に差し替え可能 ・DI(依存性注入)とも相性が良く、モジュール化しやすい

メリット

・処理を段階ごとに分離でき、コードが読みやすくなる 1ステップ=1責務になるので単機能・小さな単位で理解しやすい ・ステップを組み替えたり差し替えたりするのが簡単になる 柔軟な構成変更ができ、保守性が高い ・処理の追加が容易になる 新しいステップを足すだけなので最小変更で拡張できる ・共通処理の再利用がしやすい バリデーション、ログ、フィルタリングなどをパイプとして汎用化できる ・動的にパイプラインを構築することで、環境や条件に応じて処理フローを切り替えられる プラグイン構造や設定駆動型に向いている ・テストしやすい(1ステップ単位で単体テストが可能) 大きな処理を丸ごとテストするより遥かに楽 ・処理フロー全体を“構成ファイル化”できる コードに手を入れず YAML / JSON などでパイプ構成を変更可能 ・非同期処理との相性が良い API コール → フィルタ → キャッシュ → 保存 と自然に連結できる ・並列処理や分岐パイプを追加しやすい Fan-out / Fan-in パターンに発展しやすい ・大規模処理の整理がしやすい ステップごとの可視化・トレースがやりやすく、監視ロジックも挿入しやすい

デメリット

・処理の流れが追いにくくなる ステップが増えるほど、どこで何が起きているか把握しづらい ・デバッグが困難になりやすい 途中で止まったとき「どのステップで落ちたか」が見えにくい ・ステップ間の依存が隠れやすい 前のステップでセットされた値に次のステップが密かに依存し、ブラックボックス化する ・順序変更で不具合が起きやすい 順番がちょっと変わるだけで壊れるケースがある ・状態(context)が肥大化しやすい なんでも context に積むと巨大なグローバル変数化する ・例外処理が複雑化しやすい 途中のハンドラでエラーが起きたとき、処理継続・中断・巻き戻しの判断が難しい ・「パイプラインを導入した感」だけで過剰設計に陥りがち 簡単な処理でもステップ細分化しすぎて逆に読みにくくなる ・ステップが多いと処理コストが増える 高速さが求められる場面ではオーバーヘッドになる ・動的に差し替えられるメリットが、逆に“どこからでも差し替えられてしまう危険”にもなる 運用中にチームメンバーが勝手に順番変えてバグを生むパターン ・責任の所在が曖昧になる 誰が最終結果の保証をするのか不明瞭になりがち

ChainとPipelineの違い

Chain of Responsibility

「このケースは自分が処理する」 複数のハンドラのうち、どれか1つが担当する

Pipeline

「全員で順に処理する」 各ステップは基本すべて実行 ※拡張版はどちらも使えるハイブリッド

応用アイデア

・エラー時に rollback / compensate するステップを差し込む。 ・外部APIに応じてパイプラインを組み替える。 ・AI前処理パイプライン(text cleaning → tag抽出 → prompt生成)を動的構築。 ・ゲーム開発でのイベント処理チェーン。

あとがき

PipelineもChainも、似て非なる処理ですが、関係性のあるパターンとして覚えておくと、設計効率が増しますね。 小規模設計には不向きですが、このパターンを基本的に設計思考に組み込んでおくと拡張する場合などの効率がアップするでしょう。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ