概要
ストラテジーパターン(Strategy Pattern)は、アルゴリズムのファミリーを定義し、それぞれをカプセル化して相互に置き換え可能にする設計パターンです。これにより、アルゴリズムの変更がアルゴリズムを使用するクライアントに影響を与えなくなります。このパターンは振る舞いに関するデザインパターンに分類されます。
ストラテジーパターンはオブジェクト指向の継承と多態性のメカニズムを活用し、同じ振る舞いが異なる状況で異なる実装を持つことを可能にします。
適用シナリオ
ストラテジーパターンは、複数の類似アルゴリズムが存在する状況で、if...elseやswitch...caseの複雑さと肥大化を解決します。実際のビジネス開発では、以下のシナリオに適しています:
- 同一タイプの問題に対して複数の処理方式があり、それぞれが独立して問題を解決できる場合
- アルゴリズムを自由に切り替える必要がある場合
- アルゴリズムのルールをクライアントから隠蔽したい場合
UMLクラス図
UMLクラス図からわかるように、ストラテジーパターンは主に3つの役割で構成されます:
- コンテキスト(Context):ストラテジを操作するコンテキスト環境。高層モジュール(クライアント)がストラテジやアルゴリズムに直接アクセスすることを防ぎ、可能性のある変更をカプセル化します。
- 抽象ストラテジ(Strategy):ストラテジやアルゴリズムの振る舞いを規定します。
- 具象ストラテジ(ConcreteStrategy):具体的なストラテジやアルゴリズムの実装。
フレームワークソースコードでの実装例
1. JavaのComparatorインターフェース
public interface SortComparator<T> {
int evaluate(T first, T second);
// その他のメソッド
}
SortComparator抽象クラスの下には多くの実装クラスがあります。通常、SortComparatorをソートストラテジとしてパラメータとして渡します。
- Collectionsクラスのsortメソッド
public class CollectionsUtil {
public static <T> void customSort(List<T> list, SortComparator<? super T> comparator) {
list.sort((a, b) -> comparator.evaluate(a, b));
}
}
- TreeMapのコンストラクタ
public class CustomTreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V> {
private final SortComparator<? super K> sortComparator;
public CustomTreeMap(SortComparator<? super K> comparator) {
this.sortComparator = comparator;
}
}
2. Springフレームワークでの応用
Springフレームワークでは、Beanのインスタンス化戦略としてInstantiationStrategyインターフェースが使用されています。
public interface BeanInstantiationStrategy {
Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner);
Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner,
Constructor<?> ctor, Object... args);
}
SimpleInstantiationStrategyはこのインターフェースを実装し、基本的なコンストラクタによるBeanのインスタンス化を行います。CglibSubclassingInstantiationStrategyはSimpleInstantiationStrategyを継承し、CGLIBを使用してプロキシクラスを生成し、メソッドインジェクションをサポートします。
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, String beanName, BeanFactory owner) {
// CGLIBを使用した実装
return Enhancer.create(bd.getBeanClass(), new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
}
}
長所と短所
長所:
- 開閉原則(Open-Closed Principle)に適合しています
- if...else文やswitch文などの条件分岐を回避できます
- アルゴリズムの機密性と安全性を向上させます
短所:
- クライアントはすべてのストラテジを認識し、使用するストラテジクラスを自行で決定する必要があります
- 多くのストラテジクラスが生成され、保守が複雑になります