Strategyパターン


 ★★☆

Strategyパターン

アルゴリズムの集合を定義し、交換可能にする

Strategy とは英語で「戦略」を意味する言葉です。「アルゴリズム」と考えていいでしょう。Strategy パターンを利用することで、戦略の切り替えや追加が簡単に行えるようになります。



普通にプログラミングしていると、メソッドの中に溶け込んだ形でアルゴリズムを実装してしまうことがよくあります。if 文などで分岐させることでアルゴリズムを変更するような方法です。Strategy パターンでは、戦略の部分を意識して別クラスとして作成するようにしています。戦略部分を別クラスとして作成しておき、戦略を変更したい場合には、利用する戦略クラスを変更するという方法で対応します。Strategy パターンを利用することで、メソッドの中に溶け込んだ形のアルゴリズムより柔軟でメンテナンスしやすい設計となります。

実際には、最近のクラスライブラリは非常に充実しているため、アルゴリズムを実装する機会というのは少ないかもしれません。しかし、組み込み系のプログラミングでは既存のクラスライブラリをそのまま使うわけにはいかず、必要な部分だけ抽出するといったこともあると思います。


そういった場合にStrategyパターンを知っていれば、デスクトップ用には既存のクラスライブラリのアルゴリズムを利用できるようにし、組み込み系用には自作のクラスライブラリのアルゴリズムを利用できるようにする、といったことも可能になります。もちろん、利用するクラスライブラリの切り替えをしたいという単純な場合も適用できます。


Strategy パターンを使えば、プログラムの実行中にクラスを切り替えることもできます。例えば、メモリが少ない環境ではスピードは遅いが省メモリのアルゴリズム、メモリが多い環境ではメモリを食うがスピードは速いアルゴリズムを使うといったことも考えられます。



例題

直方体同士の縦、横、高さをそれぞれ比較するクラス作ります。


実行結果
-1          







共通クラス

Hexahedron.java
public class Hexahedron { protected int length; protected int side; protected int height; public Hexahedron(int length, int side, int height) { this.length = length; this.side = side; this.height = height; } }

Strategyパターンを使用しない例

Comparison.java
public class Comparison { public int compare(int type, Hexahedron h1, Hexahedron h2) { if (type == 0) { // 縦 if (h1.length > h2.length) return 1; else if (h1.length < h2.length) return -1; else return 0; } else if (type == 1) { // 横 if (h1.side > h2.side) return 1; else if (h1.side < h2.side) return -1; else return 0; } else { // 高さ if (h1.height > h2.height) return 1; else if (h1.height < h2.height) return -1; else return 0; } } }
Main.java
public class Main { public static void main(String[] args) { Hexahedron h1 = new Hexahedron(10, 10, 10); Hexahedron h2 = new Hexahedron(20, 5, 10); Comparison comp = new Comparison(); System.out.println(comp.compare(0, h1, h2)); } }

Strategyパターンを使用した例

ICompare.java
public interface ICompare { public int compare(Hexahedron h1, Hexahedron h2); }
CompareLength.java
public class CompareLength implements ICompare { public int compare(Hexahedron h1, Hexahedron h2) { if (h1.length > h2.length) return 1; else if (h1.length < h2.length) return -1; else return 0; } }
CompareSide.java
public class CompareSide implements ICompare { public int compare(Hexahedron h1, Hexahedron h2) { if (h1.side > h2.side) return 1; else if (h1.side < h2.side) return -1; else return 0; } }
CompareHeight.java
public class CompareHeight implements ICompare { public int compare(Hexahedron h1, Hexahedron h2) { if (h1.height > h2.height) return 1; else if (h1.height < h2.height) return -1; else return 0; } }
Comparison.java
public class Comparison { private ICompare comp; public Comparison(ICompare comp) { this.comp = comp; } public int compare(Hexahedron h1, Hexahedron h2) { return comp.compare(h1, h2); } }
Main.java
public class Main { public static void main(String[] args) { Hexahedron h1 = new Hexahedron(10, 10, 10); Hexahedron h2 = new Hexahedron(20, 5, 10); Comparison comp = new Comparison(new CompareLength()); System.out.println(comp.compare(h1, h2)); } }

どの部分を比較するかを引数として与え、例えば、0ならば縦、1ならば横、2ならば高さというように決めます。そして、Comparison クラスでは、if 文によって処理を分岐し、指定された部分の比較します。

この例では、3通りの分岐しかありませんが、もっとあったらどうでしょう。とても煩雑なコードとなってしまいます。このように、メソッドの中に溶け込んだ形で、if 文の分岐を利用してアルゴリズムを変更するようにすると、とても煩雑で、メンテナンス性に乏しいソースコードとなってしまいます。

まずは、比較アルゴリズム部分をクラスとして分離します。

このとき、Strategy パターンでは分離したアルゴリズム部分が共通のインタフェースを持つようにすることが求められます。すなわち、アルゴリズムとして分離された複数のクラスが共通のインタフェースを持つ必要があります。ここでは、縦、横、高さを比較するクラスのために、ICompare インタフェースを定義しています。

そして、共通の ICompare インタフェースを持った、縦を比較するための ComparisonLength クラス、横を比較するための ComparisonSize クラス、高さを比較するための ComparisonHeight クラスを作成します。

そこで例えば、縦を比較しようとしたら、compareLength インスタンスを引数に Comparison クラスのインスタンスを生成します。そして、そのインスタンスの compare メソッドを呼び出します。そうすると、compare メソッドは、先に引き渡されたインスタンスである ComparisonLength インスタンスの compare メソッドを呼び出します。こうして、縦が比較されるわけです。


さらに別の比較をするといった、アルゴリズムの追加をする際には、同様に ICompare インタフェースを実装するクラスを追加してやれば済みます。このように、アルゴリズムの部分を別クラスとして作成することで、比較アルゴリズムの追加が簡単になり、メンテナンスの見通しも格段に良くなります。