どうもはまじです. 今回はデザインパターンのStrategyパターンについてご紹介したいと思います.
Strategyパターン
例えば,じゃんけんのプログラムを書きました.
このUserクラスは,ずっとグーを出し続ける戦略を採っています.
enum Hand {
BLOCK,
SCISSORS,
PAPER;
}
class User {
private String name;
private int wons;
public Hand getHand() {
return Hand.BLOCK;
}
}
このクラスのインスタンスだけでじゃんけん大会すると...結果は悲惨なことにw
なので,別の戦略を持ったユーザクラスを作りたい... と思ってはいけません!
※悪い例
class RandomHandUser extends User {
private static Hand[] hands;
static {
hands = new Hand[]{ Hand.BLOCK, Hand.SCISSORS, Hand.PAPER };
}
@Override
public Hand getHand() {
return hands[(int)Math.random() * 3];
}
}
さて,これで何が問題なのかJavaに詳しい人は一発でわかるでしょう.
そう,nameとwonsが名前解決できません.
何故なら,Userクラスで定義された上記フィールドはそれぞれprivateなため,子クラスに継承されません.
ならばprotectedにすればいい?いえいえ,それは設計の過ちです.
protectedはabstractクラスなど,ほぼ継承されることがわかっているときに使えばよく,Userクラスのような通常のデータはprivateであるべきです.
ではどうすればいいのでしょうか.
答えは,Userクラスの中に戦略フィールドを用意することです.
どういうことかというと,今まではUser#getHandの中で実装していましたが,これを別のクラスに委譲してやればいいのです.
まずは,「getHand()を呼べば,何かしらのHand型が返ってくるメソッドが必要」であると考えられます.
Userクラスでも同じメソッド名で呼び出しますが,そうではなく「じゃんけんの戦略に対してgetHandする」という考えを持ってみます.
そうすると,必然的に次のインターフェースが思い浮かぶことになります.
interface HandStrategy {
public Hand getHand();
}
ではこの戦略を実装した,「グーしか出さない戦略クラス」を作りましょう.
class OnlyBlockHandStrategy implements HandStrategy {
@Override
public Hand getHand() {
return Hand.BLOCK;
}
}
という感じで,どんどん戦略クラスを作っていきます.
で,これらの戦略クラスをユーザが個別に持てるようにします.
class User {
private String name;
private int wons;
private HandStrategy handStrategy;
public Hand getHand() {
return handStrategy.getHand();
}
}
で,mainのコードはこうなりますね.
public static void main(String[] args) {
User alice = new User();
alice.setHandStrategy(new OnlyBlockHandStrategy());
User bob = new User();
bob.setHandStrategy(new OnlyScissorsHandStrategy());
// …………
}
さて,ボブはアリスに全敗を喫し,高級寿司を奢らされる羽目になりました・・・.
という感じで,ひとつのクラスのとある区画(アルゴリズム)をごっそり入れ替えることができる,Strategyパターンについて紹介しました.
ちなみにこれ,こうすると見たことありませんか?
class User {
@Autowired
private HandStrategy handStrategy;
// …………
}
そうですね,Spring Frameworkでも使用されているDependency Injectionですね.
どの戦略を入れるかはDIコンテナに依存し,中身をごっそり変えることができます.
もはや無意識のうちに使ってしまうぐらいの超頻出デザインパターンなので,ぜひ理解しましょうー