概要
アルゴリズムをオブジェクトとして切り出し、実行時に差し替えられるようにする。Strategy パターンは Java の Comparator インターフェースそのものであり、最も身近なデザインパターンの1つです。ソートアルゴリズムの切り替え、料金計算ロジックの顧客別カスタマイズ、出力形式の動的変更など、同じ処理を異なるやり方で行いたい場面に適用できます。Java 8 以降はラムダ式やメソッド参照で Strategy を簡潔に定義でき、匿名クラスを書く必要がほぼなくなりました。この記事では、ソートアルゴリズムの差し替えを題材に Context と Strategy の構造を実装し、Comparator を使った実践例、record との組み合わせ、Java 21 の sealed interface でソート仕様を型安全に定義する方法を確認します。
使いどころ
ソートアルゴリズム(バブル / 選択 / マージ)を実行時に切り替え、データ量に応じた戦略で処理する
顧客種別(一般 / プレミアム / 法人)に応じた料金計算ロジックを Strategy として差し替える
帳票の出力形式(CSV / Excel / PDF)を Strategy で抽象化し、設定に応じた出力を行う
コード例
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class StrategyPatternDemo {
interface SortStrategy {
int[] sort(int[] data);
}
static class BubbleSortStrategy implements SortStrategy {
@Override public int[] sort(int[] data) {
var result = Arrays.copyOf(data, data.length);
int n = result.length;
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (result[j] > result[j + 1]) {
int tmp = result[j];
result[j] = result[j + 1];
result[j + 1] = tmp;
}
}
}
return result;
}
}
static class DataProcessor {
private SortStrategy strategy;
public DataProcessor(SortStrategy s) { strategy = s; }
public void setStrategy(SortStrategy s) { strategy = s; }
public int[] process(int[] data) {
return strategy.sort(data);
}
}
record Person(String name, int age) {}
public static void main(String[] args) {
var data = new int[]{5, 2, 8, 1, 9, 3};
var processor = new DataProcessor(
new BubbleSortStrategy());
System.out.println("バブルソート: "
+ Arrays.toString(processor.process(data)));
// ラムダ式で Strategy を直接定義
processor.setStrategy(d -> {
var sorted = Arrays.copyOf(d, d.length);
Arrays.sort(sorted);
return sorted;
});
System.out.println("Arrays.sort: "
+ Arrays.toString(processor.process(data)));
// Comparator は Strategy パターンそのもの
var persons = new ArrayList<Person>();
persons.add(new Person("田中", 35));
persons.add(new Person("佐藤", 28));
persons.add(new Person("鈴木", 42));
var byAge = new ArrayList<>(persons);
byAge.sort(Comparator.comparingInt(Person::age));
System.out.println("年齢順: " + byAge);
var byName = new ArrayList<>(persons);
byName.sort((a, b) -> a.name().compareTo(b.name()));
System.out.println("名前順: " + byName);
}
}Version Coverage
var による型推論と record でデータクラスを簡潔に定義でき、Strategy との組み合わせが書きやすくなった。
// Java 17: record + Comparator メソッド参照
record Person(String name, int age) {}
var persons = new ArrayList<Person>();
// ...
var byAge = new ArrayList<>(persons);
byAge.sort(Comparator.comparingInt(Person::age));Library Comparison
注意点
Strategy インターフェースのメソッドが1つだけなら関数型インターフェース(@FunctionalInterface)にすることでラムダ式で簡潔に記述できる
Strategy の切り替えが頻繁に発生する場合、Context の setStrategy を外部から呼ぶ設計だとスレッドセーフティの考慮が必要になる
Strategy パターンと State パターンは構造が似ている。Strategy は外部からの明示的な切り替え、State は内部状態に応じた自動切り替えが基本
ラムダ式で Strategy を定義すると簡潔だが、複雑なロジックはクラスとして分離した方がテストしやすい
FAQ
Strategy はアルゴリズム全体をオブジェクトとして差し替えます。Template Method は処理の骨格を親クラスに固定し、ステップの一部をサブクラスでカスタマイズします。委譲か継承かの違いです。
ラムダ式を static メソッドや定数フィールドに抽出し、そのメソッド/フィールドに対してテストを書きます。匿名のラムダ式を直接テストするのは困難です。
Factory メソッドや設定ファイルから Strategy を選択するのが一般的です。Context 内にハードコードすると切り替えが困難になります。