10. Strategy パターン
- 2012/04/26 一部修正しました
- 10.1 Strategy パターンとは
- 10.2 サンプルケース
- 10.3 Strategyパターンまとめ
10.1 Strategy パターンとは
第10章では、Strategy パターンを学びます。Strategy とは英語で「戦略」を意味する言葉です。Strategy パターンを利用することで、戦略の切り替えや追加が簡単に行えるようになります。
普通にプログラミングしていると、メソッドの中に溶け込んだ形でアルゴリズムを実装してしまうことがよくあります。if 文などで分岐させることでアルゴリズムを変更するような方法です。Strategy パターンでは、戦略の部分を意識して別クラスとして作成するようにしています。戦略x部分を別クラスとして作成しておき、戦略を変更したい場合には、利用する戦略クラスを変更するという方法で対応します。Strategy パターンを利用することで、メソッドの中に溶け込んだ形のアルゴリズムより柔軟でメンテナンスしやすい設計となります。
10.2 サンプルケース
状況に応じてアルゴリズムを変えなければならないことは多々あります。例えばゲームのプログラムでは、難易度の設定によって、その戦略アルゴリズムを変える必要があるでしょう。ここでは簡単に、大小の比較を行うアルゴリズムを考えてみましょう。
まずは、人間を表す Human クラスを作成します。Human クラスは、名前、身長、体重、年齢の4つのパラメータを持つものとします。
public class Human{ public String name; public int height = -1; public int weight = -1; public int age = -1; public Human(String name,int height,int weight,int age){ this.name = name; this.height = height; this.weight = weight; this.age = age; } }
さてここで、2つのHuman インスタンスが与えられた場合に、それらの大小を比較する SampleClass というクラスを考えます。
public class SampleClass{ public int compare(Human h1,Human h2){ if(h1.age > h2.age){ return 1; }else if(h1.age == h2.age){ return 0; }else{ return -1; } } }
ここでは、年齢を比較して、第一引数で渡された Human インスタンスの年齢のほうが大きければれば、1 を返し、2つの Human インスタンスの年齢が同じであれば、0 を、第2引数で渡された Human インスタンスの年齢のほうが大きければ、-1 を返します。ここでは年齢を比較してその結果を返すことだけしか考えていません。しかし、Human オブジェクトには複数のパラメータがあり、Human を比較する方法はたくさん考えられます。比較結果は、どのパラメータをどのように利用するかにより異なってしまいます。例えば、単純に年齢で比較する場合と、身長で比較する場合では異なる結果となるでしょう。
そこで、比較するパラメータを指定できるようなプログラムとすることを考えます。SampleClass は以下のようになるでしょうか。
public class SampleClass{ private int type = -1; public static final int COMPARE_AGE = 1; public static final int COMPARE_HEIGHT = 2; public static final int COMPARE_WEIGHT = 3; public SampleClass(int compareType){ this.type = type; } public int compare(Human h1,Human h2){ if(type == COMPARE_AGE){ if(h1.age > h2.age){ return 1; }else if(h1.age == h2.age){ return 0; }else{ return -1; } }else if(type == COMPARE_HEIGHT){ if(h1.height > h2.height){ return 1; }else if(h1.height == h2.height ){ return 0; }else{ return -1; } } ・・・・・ } }
どうでしょう?とても煩雑なコードとなってしまいますね。このように、メソッドの中に溶け込んだ形で、if 文の分岐を利用してアルゴリズムを変更するようにすると、とても煩雑で、メンテナンス性に乏しいソースコードとなってしまいます。Strategy パターンでは、状況に応じて、変更する必要のあるアルゴリズムの部分を、意識的に別クラスとして分離することで、アルゴリズムの修正、追加等の見通しが非常に良くなります。
サンプルケースを例にすると、まずは、比較アルゴリズム部分をクラスとして分離します。例えば、年齢を比較するための、AgeComparatorクラスを作成します。
public class AgeComparator{ public int compare(Human h1 , Human h2){ if(h1.age > h2.age){ return 1; }else if(h1.age == h2.age){ return 0; }else{ return -1; } } }
比較アルゴリズム部分を分離し、実際の比較処理は、AgeComparator に委譲できるようにしておきます。
public class MyClass{ public int compare(Human h1,Human h2){ return new AgeComparator().compare(h1,h2); } }
これだけでは、メリットはありませんし Strategy パターンにもなっていません。Strategy パターンでは、分離したアルゴリズム部分が共通のインタフェースを持つようにすることが求められます。すなわち、アルゴリズムとして分離された複数のクラスが共通のインタフェースを持つ必要があります。サンプルケースでは、年齢を比較する AgeComparator クラス以外にも、身長を比較するための HeightComparatorクラス、体重を比較するための WeightComparatorクラスなどが考えられます。これらの比較アルゴリズムを表すクラスに共通のインタフェースを持たせます。ここでは、Comparator インタフェースを定義してみます。
ソースコードは以下のようになります。
public interface Comparator{ public int compare(Human h1,Human h2); }
public class AgeComparator implements Comparator{ public int compare(Human h1 , Human h2){ if(h1.age > h2.age){ return 1; }else if(h1.age == h2.age){ return 0; }else{ return -1; } } }
public class HeightComparator implements Comparator{ public int compare(Human h1,Human h2){ if(h1.height > h2.height){ return 1; }else if(h1.height == h2.height){ return 0; }else{ return -1; } } }
さて、このようにすることで、SampleClass は以下のように記述することができます。
public class MyClass{ private Comparator comparator = null; public MyClass(Comparator comparator){ this.comparator = comparator; } public int compare(Human h1,Human h2){ return comparator.compare(h1,h2); } }
アルゴリズムを追加する際には、同様に Comparator インタフェースを実装するクラスを追加してやれば済みます。このように、アルゴリズムの部分を別クラスとして作成することで、比較アルゴリズムの追加が簡単になり、メンテナンスの見通しも格段に良くなるのがお分かりいただけるでしょうか。
10.3 Strategyパターンまとめ
Strategy パターンの一般的なクラス図は以下のようになります。
[引用] 『Java言語で学ぶ デザインパターン入門』(結城浩 ソフトバンクパブリッシング株式会社出版 2001年)