【Effective Java】項目21:戦略を表現するために関数オブジェクトを利用する
戦略パターンを実現するため、関数オブジェクトを利用する。
関数オブジェクト
多くのプログラミング言語では、関数ポインタ、委譲、ラムダ式と呼ばれるような機構を利用して、関数自体の受け渡しが可能になっている。 関数自体をある関数に与えることで、呼び出した関数の振る舞いを変更できる。
たとえば、C の qsort 関数は、要素の比較を抽象化していて、要素を比較するコンパレータ関数へのポインタを渡すことができる。 これは戦略パターンの一例である。
Java では関数ポインタは提供されていないが、同様の目的のためにオブジェクトが利用できる。
通常、オブジェクトに対するメソッドの呼び出し(オブジェクトへのメッセージング)は、そのオブジェクトに対して何らかの操作を行うために用いる。 特定の操作を行うメソッドを持つオブジェクトを、他の関数に渡すことで関数ポインタと同じ機能を提供できる。
この目的のためのオブジェクトを関数オブジェクトと呼ぶ。
具象戦略クラス
以下のクラスの compare メソッドは長さに基づく順序を定義している。
class StringLengthComparator { public int compare(String s1, String s2) { return s1.length() - s2.length(); } }
このクラスのインスタンスは、文字列比較に対する具象戦略となる。
具象戦略クラスは一般的に状態を持たない。 すべてのインスタンスが機能的には同じになるため、シングルトン(項目3、項目5)にすることができる。
class StringLengthComparator { private StringLengthCoparator() {} public static final StringLengthComparator INSTANCE = new StringLengthComparator(); public int compare(String s1, String s2) { return s1.length() - s2.length(); } }
戦略インタフェース
上の StringLengthComparator インスタンスをメソッドに渡すためには適切な型が必要である。 StringLengthComparator を型として利用すると、長さによる比較しかできなくなる。
そこで、戦略インタフェースと呼ばれるインタフェースを定義する。
public interface Comparator<T> { public int compare(T t1, T t2); }
この Comparator インタフェースは java.util にも存在するが、自分で定義することも可能である。 ジェネリクスを利用しているので、String 以外のコンパレータとしても機能する。
Comparator を使った実装は以下のようになる。
class StringLengthComparator implements Comparator<String> { ...(省略)... }
無名クラスによる具象戦略
具象戦略クラスは、多くの場合に無名クラスを利用する。
Array.sort(array, new Comparator<String>() { public int compare(String s1, String s2) { return s1.length() - s2.length(); } });
ただし、このコードでは、実行する度に新しいインスタンスが生成される。 もし、繰り返し実行するのであればシングルトンにするべきである。
また、具象戦略クラスを public にする必要はない。 かわりに、ホストクラスを作成し public static final Comparator メンバとして公開可能である。
public class Host { private static class StrLenCmp implements Comparator<String> { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } } public static final Comparator<String> STRING_LENGTH_COMARATOR = new StrLenCmp(); }
実際、Stirng クラスは大文字・小文字を区別しない文字列コンパレータ CASE_INSENSITIVE_ORDER をこのパターンで実装している。
感想
確かに String.java を見ると、そうなっていた > CASE_INSENSITVE_ORDER
GC: String - java.lang.String (.java) - GrepCode Class Source