今日の目標
JavaのStrategyパターンとはどういったものなのかを知る
使うもの
ではスタート
はじめに
まずはStrategyパターンとはどのようなものなのかをWikipediaさんで調べてみる。
Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。(wikipedia-Strategyパターン)
アルゴリズムのパターンをある程度用意しておいて、動的に切り替えられるようにするパターンのことかな。何となくわかるような。
本のサンプルを参考に自分で作ってみる
今回張り切ってサンプルを参考に自分で作ってみました。が、サンプルのほうがわかりやすいと思うので、わからなかったら本のほうを参考にしてください…。(本のサンプルはじゃんけんゲームです)
今回作るのは、テレビゲームのカジノとかでよく見かけるアップダウンゲームです。アップダウンゲームとは、トランプを一枚表にし、次にめくるカードがそのカードより「上か、下か」を当てる簡単なゲームです。これをStrategyパターンを使って作りました。
※本来ならトランプはA~Kまでですが、今回面倒だったので1~13という数字になってます。また、AはKより強いのですが、数字にした関係でAに当たる1は一番弱くなってます。ご了承ください
Strategyインタフェースは、shoutメソッドだけ定義してます。内部の実装は各実装クラスにお任せして、とりあえず「上だ!」「下だ!」ってことを叫んでね、ということ。
package strategy;
import util.ShoutWord;
public interface Strategy {
// 受け取ったnumberによってUPとDOWNどちらを宣言するか
public abstract ShoutWord shout(int number);
}
1つ目のStrategy実装はこんな感じ。単純。
- 1~6が出た場合⇒次のトランプはそれよりも上であると判断する
- 7~13が出た場合⇒次のトランプはそれよりも下であると判断する
package strategy;
import util.ShoutWord;
public class HalfStrategy implements Strategy {
@Override
public ShoutWord shout(int number) {
ShoutWord shoutWord;
// もし数字が7以上だったら次のカードはDOWNと宣言する
if(number>=7) {
shoutWord = ShoutWord.DOWN;
} else {
shoutWord = ShoutWord.UP;
}
return shoutWord;
}
}
2つ目のStrategy実装はこれ。
- 1~4が出た場合⇒次のトランプはそれよりも上であると判断する
- 10~13が出た場合⇒次のトランプはそれよりも下であると判断する
- 5~9が出た場合⇒次の数字をランダムで予測して、予測した数字のほうが大きかったら上であると判断して、予測した数字のほうが小さかったら下であると判断する
package strategy;
import java.util.Random;
import util.ShoutWord;
public class OneThirdStrategy implements Strategy {
@Override
public ShoutWord shout(int number) {
ShoutWord shoutWord;
if(number>=10) {
// 1-4: 上
shoutWord = ShoutWord.DOWN;
} else if(number<=4){
// 10-13: 下
shoutWord = ShoutWord.UP;
} else {
// 5-9: ランダム数字を生成し、次の数字を予想する
Random random = new Random();
int next = random.nextInt(13) + 1;
if(number<=next) {
shoutWord = ShoutWord.UP;
} else {
shoutWord = ShoutWord.DOWN;
}
}
return shoutWord;
}
}
PlayerはカードがめくられたらStrategyにお伺いを立てて、上か下かを叫びます。shout!
package strategy;
import util.ResultWord;
import util.ShoutWord;
public class Player {
private String name;
private Strategy strategy;
private int wincount;
private int losecount;
private int gamecount;
public Player(String name, Strategy strategy) {
this.name = name;
this.strategy = strategy;
}
public ShoutWord shout(int number) {
return strategy.shout(number);
}
public void win() {
wincount++;
gamecount++;
}
public void lose() {
losecount++;
gamecount++;
}
public ResultWord result() {
if(wincount>losecount) {
return ResultWord.OK;
}
return ResultWord.NG;
}
public String toString() {
return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose.";
}
}
各enum。
package util;
public enum ShoutWord {
UP,
DOWN
}
package util;
public enum ResultWord {
OK,
NG
}
実行するMainクラス。1000回勝負したうち、勝った回数と負けた回数を出力してます。
途中で、もし結果が「負けが多い」ということであれば(Playerのresultメソッド呼び出し)、別の戦略に乗り換えるようにしてます。OneThirdStrategyからHalfStrategyへ。(ロジックはコピペしてます。ホントはいけないんですが、今回はご容赦を)
import java.util.Random;
import strategy.HalfStrategy;
import strategy.OneThirdStrategy;
import strategy.Player;
import util.ResultWord;
import util.ShoutWord;
public class Main {
public static void main(String[] args) {
Random rnd = new Random();
Player player = new Player("torinist", new OneThirdStrategy());
int trumpNumberOld = rnd.nextInt(13) + 1;
for(int i=0;i<1000;i++) {
int trumpNumberNew = rnd.nextInt(13) + 1;
ShoutWord shoutWord = player.shout(trumpNumberOld);
// 判定
if(trumpNumberOld<trumpNumberNew) {
// もし新しいカードのほうが数字が大きい場合
if(ShoutWord.UP==shoutWord) {
player.win();
} else {
player.lose();
}
} else if(trumpNumberOld>trumpNumberNew) {
// もし新しいカードのほうが数字が小さい場合
if(ShoutWord.DOWN==shoutWord) {
player.win();
} else {
player.lose();
}
} else {
// 同じ数字の場合は自動的にPlayerがwinになる
player.win();
}
trumpNumberOld = trumpNumberNew;
}
if(ResultWord.NG == player.result()) {
System.out.println("HalfStrategyに移行します。");
player = new Player("torinist", new HalfStrategy());
trumpNumberOld = rnd.nextInt(13) + 1;
for(int i=0;i<1000;i++) {
int trumpNumberNew = rnd.nextInt(13) + 1;
ShoutWord shoutWord = player.shout(trumpNumberOld);
// 判定
if(trumpNumberOld<trumpNumberNew) {
// もし新しいカードのほうが数字が大きい場合
if(ShoutWord.UP==shoutWord) {
player.win();
} else {
player.lose();
}
} else if(trumpNumberOld>trumpNumberNew) {
// もし新しいカードのほうが数字が小さい場合
if(ShoutWord.DOWN==shoutWord) {
player.win();
} else {
player.lose();
}
} else {
// 同じ数字の場合は自動的にPlayerがwinになる
player.win();
}
trumpNumberOld = trumpNumberNew;
}
}
System.out.println("Total result: " + player.toString());
}
}
結果はこんな感じ。ぶっちゃけOneThirdStrategyが負けることがないので、HalfStrategyには移行しません。残念。
Total result: [torinist:1000 games, 782 win, 218 lose.
注目すべきは、Mainの中でPlayerが持つべきStrategyを変更しているということですかね。たぶんこの動的な切り替えがStrategyパターンなのだと理解してます。違ったら訂正して頂けるとありがたいです。
もし、戦略を増やしたい!ということであれば、Strategyインタフェースを実装したクラスを作って、MainでPlayerに渡すStrategy実装を変えれば良いのですね。
今日はこんな感じで!