デザインパターンの章

6.4. Strategy

アルゴリズムをごっそり差し替えるために使えるパターンです。

オブジェクトのアルゴリズムをカプセル化し、 アルゴリズムを実装した部分を動的に交換して切り替えることを容易にするためのパターンです。 切り替えられるのがミソといえるパターンであり「コールバック」を実現するために利用されます。
C言語で言うところの関数ポインタの代替といえます。

 ※コールバック=内部で処理の全てを行わず外部へ処理や判断を行わせること

 Java API での使用例
   java.util.zip.CheckedInputStream
   java.util.zip.CheckedOutputStream

■クラス図

■サンプルソース

これは、戦略インターフェースです。

public interface Comparator<T> {
    int compare(T o1, T o2);
}

「戦略」のための抽象的なメソッドを定義します。
ここでは、大小関係を比較することとします。

これは、具象戦略を提供する1つ目のクラスです。

public class Foo implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        return Integer.compare(o1.length(), o2.length());
    }
}

具体的な「戦略」を実装します。
文字列の長さを比べます。

これは、具象戦略を提供する2つ目のクラスです。

public class Bar implements Comparator<String> {
    @Override
    public int compare(String o1, String o2) {
        return o1.compareTo(o2);
    }
}

文字列を辞書式に比べます。

これは、戦略を利用するクラスです。

OpenJDK7のjava.util.Arrays クラスにおけるマージソートの実装ですが、詳細を追う必要はありません。
public class Arrays {
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            TimSort.sort(a, c);
    }

    private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
        T[] aux = a.clone();
        if (c==null)
            mergeSort(aux, a, 0, a.length, 0);
        else
            mergeSort(aux, a, 0, a.length, 0, c);
    }
    
    private static void mergeSort(Object src[], Object dest[],
        int low, int high, int off, Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i=low; i<high; i++)
                for (int j=i; j>low && 
                    c.compare(dest[j-1], dest[j])>0; j--) {
                    swap(dest, j, j-1);
                }
            }
            return;
        }

        // Recursively sort halves of dest into src
        int destLow  = low;
        int destHigh = high;
        low  += off;
        high += off;
        int mid = (low + high) >>> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);

        // If list is already sorted, just copy from src to dest.
        // This is an optimization that results in faster sorts 
        // for nearly ordered lists.
        if (c.compare(src[mid-1], src[mid]) <= 0) {
           System.arraycopy(src, low, dest, destLow, length);
           return;
        }

        // Merge sorted halves (now in src) into dest
        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid &&
                c.compare(src[p], src[q]) <= 0) {
                dest[i] = src[p++];
            } else {
                dest[i] = src[q++];
            }
        }
    }
}

節目節目で、どのような動作をすればよいのかを戦略クラスに問い合わせに行きます。
ソートする処理は実装していますが、前後関係をどうやって判断するかまでは実装していません。

■実装のミソ

1.
具象戦略が一度しか使われないのであれば無名クラスとして作成しておくとよいでしょう。

2.
具象戦略が繰り返されるならファクトリメソッドを通じて具象戦略クラスを返すとよいでしょう。

class Host {
    private static Foo foo = new Foo();
    
    private static class Foo implements Comparator {
        // :
    }
    
    public static Foo getFoo() {
        return foo;
    }
}

■利用例

void Test() {
    String[] stringArray = new String[]{"ccc", "a", "bb"};

    Comparator c1 = new Foo(); // 文字列長ソート
    Comparator c2 = new Bar(); // 辞書式ソート
    
    Arrays.sort(stringArray, c1);
    
    Arrays.sort(stringArray, c2);
}

Foo、Bar ともに Comparator インターフェースを実装した具象戦略クラスです。
Arrays からすると Foo、Bar を切り替えることで 違ったアルゴリズムでソート処理をすることができます。

Strategy パターンのいいところは、 アルゴリズムの部分を他の部分と意識的に分離することです。 この例でいえば Foo、 Bar にアルゴリズムを分離していますね。

Arrays 単品では sort という API しか用意しませんが、 Comparator というアルゴリズムを実装している部分に処理を委譲することで アルゴリズムを駆動させます。

このパターンを使わなかったら、 ヘタするとこのように、API とアルゴリズムが密着しかねませんね。

    Arrays.sortByFoo(stringArray);
    
    Arrays.sortByBar(stringArray);

委譲によって結びつきが緩やかになっているので、 アルゴリズムを容易に(動的にでも)サクッと切り替えることができます。 高速アルゴリズムがあとから登場したとしても、 差し替えが容易なので簡単に切り替えて試せますね。

■注意点

Template Method パターンと Strategy パターンは「アルゴリズムを変更可能にする」という共通の目的がありますが、これを達成する方法が違います。

メソッドのオーバーライドにより処理内容を変化させるのが Template Method パターンです。
委譲先オブジェクトの取り替えにより処理内容を変化させるのが Strategy パターンです。

< 前のページへ

Pagetop