概要

アルゴリズムをオブジェクトとして切り出し、実行時に差し替えられるようにする。Strategy パターンは Java の Comparator インターフェースそのものであり、最も身近なデザインパターンの1つです。ソートアルゴリズムの切り替え、料金計算ロジックの顧客別カスタマイズ、出力形式の動的変更など、同じ処理を異なるやり方で行いたい場面に適用できます。Java 8 以降はラムダ式やメソッド参照で Strategy を簡潔に定義でき、匿名クラスを書く必要がほぼなくなりました。この記事では、ソートアルゴリズムの差し替えを題材に Context と Strategy の構造を実装し、Comparator を使った実践例、record との組み合わせ、Java 21 の sealed interface でソート仕様を型安全に定義する方法を確認します。

使いどころ

ソートアルゴリズム(バブル / 選択 / マージ)を実行時に切り替え、データ量に応じた戦略で処理する

顧客種別(一般 / プレミアム / 法人)に応じた料金計算ロジックを Strategy として差し替える

帳票の出力形式(CSV / Excel / PDF)を Strategy で抽象化し、設定に応じた出力を行う

コード例

StrategyPatternDemo.java
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);
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

var による型推論と record でデータクラスを簡潔に定義でき、Strategy との組み合わせが書きやすくなった。

Java 17
// 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

標準 API(interface + ラムダ式)アルゴリズムの種類が少なく、ラムダ式で簡潔に定義できる場面。外部依存なしで完結する。アルゴリズムの組み合わせや設定が複雑になると管理コードが増える。
Comparator チェーンソートの多軸条件を thenComparing で連結する場合。標準 API で宣言的に書ける。ソート以外の戦略切り替えには適用できない。
Function / BiFunction汎用的な関数型インターフェースで戦略を受け渡す場合。専用の Strategy interface を定義するほどでもない場面。引数・戻り値の意味が型から読み取りにくく、専用 interface の方がドキュメント性が高い。

注意点

Strategy インターフェースのメソッドが1つだけなら関数型インターフェース(@FunctionalInterface)にすることでラムダ式で簡潔に記述できる

Strategy の切り替えが頻繁に発生する場合、Context の setStrategy を外部から呼ぶ設計だとスレッドセーフティの考慮が必要になる

Strategy パターンと State パターンは構造が似ている。Strategy は外部からの明示的な切り替え、State は内部状態に応じた自動切り替えが基本

ラムダ式で Strategy を定義すると簡潔だが、複雑なロジックはクラスとして分離した方がテストしやすい

FAQ

Strategy パターンと Template Method パターンの違いは何ですか。

Strategy はアルゴリズム全体をオブジェクトとして差し替えます。Template Method は処理の骨格を親クラスに固定し、ステップの一部をサブクラスでカスタマイズします。委譲か継承かの違いです。

ラムダ式で定義した Strategy をテストするにはどうすればよいですか。

ラムダ式を static メソッドや定数フィールドに抽出し、そのメソッド/フィールドに対してテストを書きます。匿名のラムダ式を直接テストするのは困難です。

Strategy の選択をどこで行うべきですか。

Factory メソッドや設定ファイルから Strategy を選択するのが一般的です。Context 内にハードコードすると切り替えが困難になります。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます