ソフトウェア開発において、「アルゴリズムや処理の切り替えを柔軟に行いたい」という要求は非常によく起こります。
そのような場面で威力を発揮するのが、GoF(Gang of Four)のデザインパターンのひとつである「ストラテジパターン(Strategy Pattern)」です。
ストラテジパターンはポリモーフィズムとカプセル化を巧みに活用した設計パターンであり、オブジェクト指向設計の原則を実践するうえでも非常に重要な知識です。
本記事では、ストラテジパターンの意味と構造、GoFデザインパターンにおける位置づけ、アルゴリズム切り替えの仕組み、実装方法をわかりやすく解説していきます。
ストラテジパターンとは?結論として「アルゴリズムをカプセル化して切り替え可能にする設計パターン」
それではまず、ストラテジパターンとは何かについて、結論から解説していきます。
ストラテジパターンとは、アルゴリズムや処理のロジックをそれぞれ独立したクラスにカプセル化し、実行時に切り替えられるようにするGoFのデザインパターンです。
GoFの書籍では「一連のアルゴリズムを定義し、それぞれをカプセル化して互いに交換可能にする」と定義されています。
ストラテジパターンの主要な登場人物は以下の3つです。
Context(コンテキスト):ストラテジ(戦略)を使用するクラスです。どのストラテジを使うかを知っていますが、その実装の詳細は知りません。
Strategy(ストラテジインターフェース):すべての具体的なストラテジが実装すべきインターフェースを定義します。
ConcreteStrategy(具体的なストラテジ):Strategyインターフェースを実装した個々のアルゴリズムのクラスです。
Contextは具体的なアルゴリズムを知らず、Strategyインターフェースを通じて処理を委譲するだけです。
このため、新しいアルゴリズムを追加する際も、Contextを変更せずに新しいConcreteStrategyクラスを追加するだけで対応できるでしょう。
GoFデザインパターンにおける位置づけ
GoF(Gang of Four)は、1994年に出版された「Design Patterns: Elements of Reusable Object-Oriented Software」の著者グループの通称です。
GoFのパターンは「生成」「構造」「振る舞い」の3カテゴリに分類されており、ストラテジパターンは「振る舞い」カテゴリに属するでしょう。
振る舞いパターンは、クラスやオブジェクト間の責任の分担とアルゴリズムの定義に関するパターンです。
StrategyパターンはTemplateMethodパターンやCommandパターンと並んで、振る舞いパターンの中でも特に広く使われる重要なパターンといえます。
ポリモーフィズムを活用したアルゴリズム切り替えの仕組み
ストラテジパターンの核心は、ポリモーフィズムを使ったアルゴリズムの切り替え機能にあります。
Strategyインターフェースに対してプログラミングすることで、ConcreteStrategyのどの実装が渡されても、Contextは同じ方法でアルゴリズムを呼び出せるでしょう。
実行時にContextに渡すConcreteStrategyを変えるだけで、アルゴリズムを動的に切り替えられます。
これはSOLID原則の開放閉鎖の原則(OCP)を実現する典型的なパターンであり、Contextのコードを一切変えずに新しいアルゴリズムを追加できます。
カプセル化によるアルゴリズムの隠蔽
ストラテジパターンでは、各アルゴリズムの実装の詳細がConcreteStrategyクラスの中にカプセル化されています。
Contextはアルゴリズムがどのように実装されているかを知る必要がなく、インターフェースのメソッドを呼ぶだけでよいという設計になっています。
カプセル化によって、アルゴリズムの実装変更がContextに影響を与えないという疎結合が実現されます。
また、各ConcreteStrategyを独立してテストできるため、テスタビリティの向上にも大きく貢献するでしょう。
ストラテジパターンの実装方法と具体例
続いては、ストラテジパターンの具体的な実装方法を確認していきます。
ソートアルゴリズムへの適用例
ストラテジパターンの典型的な適用例として、ソートアルゴリズムの切り替えがあります。
TypeScriptでのストラテジパターン実装の概念例を示します。
interface SortStrategy { sort(data: number[]): number[]; }
class BubbleSortStrategy implements SortStrategy { sort(data: number[]) { バブルソートの実装 } }
class QuickSortStrategy implements SortStrategy { sort(data: number[]) { クイックソートの実装 } }
class Sorter { constructor(private strategy: SortStrategy) {} sort(data: number[]) { return this.strategy.sort(data); } }
使い方:new Sorter(new QuickSortStrategy()).sort([3,1,2]);
データの規模や性質に応じて最適なソートアルゴリズムを選択できる柔軟な設計が、ストラテジパターンによって実現されるでしょう。
新しいソートアルゴリズムを追加する場合も、SortStrategyを実装した新しいクラスを作るだけで対応できます。
支払い方法への適用例
ECサイトや決済システムにおける支払い方法の切り替えも、ストラテジパターンの代表的な適用例です。
クレジットカード・PayPal・銀行振込など複数の支払い方法を、StrategyインターフェースのConcreteStrategyとして実装し、実行時に切り替える設計が実現できます。
新しい支払い方法を追加する際も、既存のコードを変更せずに新しいStrategyクラスを追加するだけで対応できます。
ビジネス要件の変化に対して柔軟に対応できる設計として、現実のビジネスアプリケーションで広く活用されているでしょう。
JavaScriptでの関数を使ったシンプルな実装
JavaScriptでは、クラスを使わずに関数をStrategyとして渡すシンプルな実装も可能です。
関数が一級市民であるJavaScriptでは、コールバック関数としてStrategyを渡すパターンが自然に実現でき、コードが非常に簡潔になるでしょう。
Array.prototype.sortにcomparator関数を渡すのも、広い意味ではストラテジパターンのシンプルな応用例といえます。
TypeScriptを使えば、Strategy関数の型を明示することで型安全なストラテジパターンが実現できます。
ストラテジパターンのメリット・デメリットと使うべき場面
続いては、ストラテジパターンのメリット・デメリットと、どのような場面で使うべきかを確認していきます。
ストラテジパターンのメリット
ストラテジパターンの主なメリットとして、まず「開放閉鎖の原則を実現できる」点が挙げられます。
新しいアルゴリズムの追加がContextの変更なしに行えるため、既存コードへの影響を最小限に抑えられるでしょう。
次に、「アルゴリズムを独立してテストできる」点も重要です。
各ConcreteStrategyは独立したクラスであるため、ユニットテストを個別に書くことができます。
「実行時にアルゴリズムを動的に切り替えられる」柔軟性も、ストラテジパターンの大きな強みのひとつです。
ストラテジパターンのデメリット
ストラテジパターンにはいくつかのデメリットもあります。
アルゴリズムの種類が少ない場合、StrategyインターフェースとConcreteStrategyクラスを作ることがオーバーエンジニアリングになる可能性があります。
クライアントコードがどのStrategyを使うかを知っている必要があり、適切なStrategyを選択する責任がクライアントに生じます。
クラスの数が増えることで、コードベースの複雑さが増す場合もあるでしょう。
ストラテジパターンを使うべき場面
ストラテジパターンが特に有効な場面は、条件分岐(if-elseやswitch文)でアルゴリズムを切り替えているコードを見かけた時です。
アルゴリズムが今後追加・変更される可能性が高い場合にも、ストラテジパターンを採用する価値があります。
テスト対象のアルゴリズムを差し替えてユニットテストしたい場合にも、ストラテジパターンが有効な設計といえるでしょう。
| パターン | 特徴 | 主な用途 |
|---|---|---|
| ストラテジ | アルゴリズムを切り替え可能にする | 処理方法の動的切り替え |
| テンプレートメソッド | 処理の骨格を定義し詳細をサブクラスに委ねる | 共通フローの定義 |
| コマンド | 操作をオブジェクトとしてカプセル化 | 取り消し・ログ記録 |
| デコレーター | オブジェクトに動的に機能を追加 | 機能の動的拡張 |
まとめ
本記事では、ストラテジパターンの意味と構造、GoFデザインパターンにおける位置づけ、ポリモーフィズムとカプセル化を活用した実装方法、メリット・デメリットを解説しました。
ストラテジパターンは「アルゴリズムをカプセル化して切り替え可能にする設計パターン」であり、開放閉鎖の原則を実現する代表的な手法です。
条件分岐でアルゴリズムを切り替えているコードをリファクタリングする際にも、ストラテジパターンは非常に有効な設計指針となるでしょう。
ストラテジパターンを習得することで、変更に強く・テストしやすい・拡張しやすいオブジェクト指向設計が実践できるようになるでしょう。