The King's Museum

ソフトウェアエンジニアのブログ。

【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

(c) The King's Museum