デザインパターン「Strategy 」
ここに書いてある内容は個人のアウトプットを吐き出している場です。 違うことが多々あると思いますが、温かい目で見てください。
よくある?だめな例
例えば犬に関するプログラムを書いたとき。
抽象クラスを作り、継承を使っていく。
public abstract class Dog { abstract void bark(); abstract void walk(); abstract void sit(); abstract void jump(); }
抽象クラスで犬の行う行動を定義して、その後継承を行って実装します。
実際に以下で普通の犬を実装してみました。
public class NormalDog extends Dog{ void bark() { System.out.println("ワン!"); } void walk() { System.out.println("普通に歩きます"); } void sit() { System.out.println("おすわりします"); } void jump() { System.out.println("飛びます"); } }
歩けない犬がいることに気がつく
ここで、犬には歩けないかつ飛べない犬がいる事に気が付きました。
ですので、抽象クラスであるDog.javaを継承してNotWalkableDog.javaを作ることにしました。
public class NotWalkableDog extends Dog{ void bark() { System.out.println("ワン!"); } void walk() { System.out.println("歩けません"); } void sit() { System.out.println("おすわりします"); } void jump() { System.out.println("飛べません"); } }
更に別の種類の犬がいることに気がつく
さらに歩けるけど、飛べない老犬がいることに気が付き実装することにしました。
ここでは冗長になるのでソースコードを省略しますが、抽象クラスであるDogを継承しオーバーライドして中身を老犬用に書き換えます。
この方法の問題点
今の所はそんなに複雑じゃないかもしれませんが、新しいDogのサブクラスを作る度にオーバーライドしてプログラミングする必要が出てきます。これが2,3個だと問題ないかも知れませんが何十個もする必要が現れると面倒くさくなります。
更にwalk()の中身を書き換えようと思うとすべてのサブクラスに対して変更が必要となってしまいます。
Dogのサブクラス毎にwalk()とjump()の中身が変わるため、継承があまり機能していない事がわかります。
解決策
上のプログラムの何が問題かというと、変化する部分に対して適切なカプセル化が行われていないということです。
この場合は例を簡単にしてすべての犬は吠えることが出来ておすわりすることができるとします。
つまりbark()とsit()は不変なものですが、walk()とjump()は変化する部分であると捉えます。
そこでwalk()とjump()をDogクラスから取り出して、これらの代わりとなる新しいクラス群を作成します。
それぞれの振舞い用インタフェースを作成する
ここではwalk()とjump()は変化する部分であるのでwalkの振舞い用インタフェースと、 jumpの振舞い用インタフェースを作成します。
public interface WalkBehavior { public void walk(); }
public interface JumpBehavior { public void jump(); }
振舞い用インタフェースを実装する
作成したインタフェースに対して実際の振舞を実装していきます。 例えばWalkBehaviorに対しては、下記の用に普通に歩く振舞いのクラスと歩けない振舞いのクラスを実装します。
public class WalkNormal implements WalkBehavior{ public void walk() { System.out.println("普通に歩きます"); } }
public class WalkNo implements WalkBehavior{ public void walk() { System.out.println("歩けません"); } }
こうすることによって振舞いの実際に行った実装(今回の場合は歩けるor歩けないとか飛べるor飛べない)が、Dogクラスに束縛されません。
それぞれの振舞いを統合する。
Dogクラスの中で振舞いを決めるのではなく、振舞いクラスであるWalkNormalやWalkNoに振舞いを委譲しています。 また、今回はすべての犬は鳴いておすわりすると定義しているのでDogくらすはこのように書き換えることが出来ます。 (実際の設計では鳴かない犬とか座らない犬も考慮するべきですが、今回はめんどくさかったのでこうしてます)
public abstract class Dog { WalkBehavior walkBehavior; //インタフェース型の変数を宣言 JumpBehavior jumpBehavior; public void bark(){ System.out.println("ワン!"); }; public void sit(){ System.out.println("おすわりします"); }; public void walk(){ walkBehavior.walk(); //walkの振舞いクラスに処理を委譲,歩くことだけを知っている }; public void jump(){ jumpBehavior.jump(); //jumpの振舞いクラスに処理を委譲,飛ぶことだけを知っている }; }
このように、Dogクラスの中に振舞いとしてのWalkBehaviorとJumpBehaviorを持つことによって 振舞いを別の振舞いクラスに委譲することが出来ます。
実際に歩けて飛べる普通の犬を作ってみる
この普通の犬はWalkNormalクラスとJumpNormalクラスを使ってそれぞれ歩く振舞いと飛ぶ振舞いを処理しています。
public class NormalDog extends Dog{ public NormalDog(){ walkBehavior = new WalkNormal(); jumpBehavior = new JumpNormal(); } }
ここではDogクラスから継承されたwalkBehaviorとjumpBehaviorのインスタンス変数を、コンストラクタがそれぞれのインスタンスに初期化しています。
Dogクラスにインスタンス変数を持たなくともそれぞれのサブクラスで持ってもいいですが、何個もサブクラスを作ると多くなってしまうのでこちらのほうが少ないと思います。
実際に犬を歩かせて飛ばせて泣かせてみる
public class Main { public static void main(String[] args){ Dog dog = new NormalDog(); dog.walk(); dog.jump(); dog.bark(); } }
実行結果は
普通に歩きます 飛びます ワン!
となってちゃんと歩いて飛んで鳴きました。
このようにインタフェースに対して振舞いを実装することによって、
例えwalk()の処理内容が「普通に歩きます」から「変わった歩き方ですが歩きます」に変わったとしても大丈夫です。
まとめ
Strategy パターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。
これはwikiからの引用です。
つまり今回の場合は「歩く」「飛ぶ」というStrategyのアルゴリズムのインタフェースを作成し、実際のアルゴリズムの動作として「歩く」「歩けない」を実装しました。
これらの「歩く」「歩けない」というのは実行時(Dogクラスにsetterを作っておけば呼び出しを行うことによって各動作を設定できる)に選択できるということだと解釈しています。