Index
Java言語で学ぶデザインパターン入門第3版
参考書籍では以下のように説明されています。
実装したアルゴリズムをごっそり交換できるようになっています。
アルゴリズムをカチッと切り替え、同じ問題を別の方法で解くのを容易にするパターン、それがstrategyパターンなのです
- アルゴリズムを容易に切り替えることができる
- 実行中にアルゴリズムを切り替えることもできる
詳しくはサンプルコードを見ながら説明していきます。
ジャンケンの戦略をStrategyパターンを使って切り替えてみたいと思います。
戦略は以下の2つです。
- FoolStrategy:毎回ランダムに出す手を決める
- SmartStrategy:次の手を確立を使って計算する
UML以下の通り
ジャンケンの手を定義するHand型を定義します。
Hand配列にはSingletonパターンが使われています。
詳細は以下の記事を参考にしてください。
Singletonパターン@Java言語で学ぶデザインパターン入門
次の手を取得するgetHand()メソッドではstatic FactoryMethodパターンが使われています。
詳細は以下の記事を参考にしてください。
FactoryMethodパターン@Java言語で学ぶデザインパターン入門
fight()メソッドを使って手の強さを判定していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public enum Hand { //ジャンケンの手を表すenum ROCK("グー",0), SCISSORS("チョキ",1), PAPER("パー",2); //enumがもつフィールド private final String m_name; private final int m_handValue; private static Hand[] hands = { ROCK,SCISSORS,PAPER }; //コンストラクタ private Hand(final String name, final int handValue) { m_name = name; m_handValue = handValue; } //手の値からenum定数を得る public static Hand getHand(final int handValue) { return hands[handValue]; } public boolean isStrongerThan(final Hand hand) { return fight(hand) == 1; } public boolean isWeekerThan(final Hand hand) { return fight(hand) == -1; } private int fight(final Hand hand) { if(this == hand) return 0; else if((m_handValue + 1) % 3 == hand.m_handValue) return 1; else return -1; } @Override public String toString() { return m_name; } } |
ジャンケンの戦略のための抽象メソッドを集めたものです。
nextHand()メソッド:次の手をきめます
study()メソッド:次の手を決めるための学習をします
1 2 3 4 |
public interface Strategy { public abstract Hand nextHand(); public abstract void study(final boolean win); } |
勝負に勝ったら次はグーを、それ以外はランダムで次の手を決めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.util.Random; public class FoolStrategy implements Strategy { private Random m_random; private boolean m_won = false; public FoolStrategy(final int seed) { m_random = new Random(seed); } @Override public Hand nextHand() { if(m_won) return Hand.getHand(0); return Hand.getHand(m_random.nextInt(3)); } @Override public void study(final boolean win) { m_won = win; } } |
次に出す手を乱数で決めますが、過去の戦歴から次に出す手の確率を変えています。
m_history[前回に出した手][今回出す手]というふうな意味を持ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
import java.util.Arrays; import java.util.Random; public class SmartStrategy implements Strategy{ private Random m_random; private int m_prevHandValue = 0; private int m_currentHandValue = 0; private int[][] m_history = { {1,1,1, }, {1,1,1, }, {1,1,1, }, }; public SmartStrategy(final int seed) { m_random = new Random(seed); } private int getSum(final int handValue) { return Arrays.stream(m_history[handValue]).sum(); } @Override public Hand nextHand() { final int bet = m_random.nextInt(getSum(m_currentHandValue)); int handValue = 0; if(bet < m_history[m_currentHandValue][0]) handValue = 0; else if (bet < m_history[m_currentHandValue][0] + m_history[m_currentHandValue][1]) handValue = 1; else handValue = 2; m_prevHandValue = m_currentHandValue; m_currentHandValue = handValue; return Hand.getHand(handValue); } @Override public void study(final boolean win) { if(win) m_history[m_prevHandValue][m_currentHandValue]++; else { if(win) m_history[m_prevHandValue][(m_currentHandValue + 1) % 3]++; if(win) m_history[m_prevHandValue][(m_currentHandValue + 2) % 3]++; } } } |
実際にじゃんけんをする人です。
実際に次の手を決めるのはStrategyクラスに任せているのがポイントです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
public class Player { private String m_name; private Strategy m_strategy; private int m_winCount; private int m_loseCount; private int m_gameCount; public Player(final String name , final Strategy strategy) { m_name = name; m_strategy = strategy; } public Hand nextHand() { return m_strategy.nextHand(); } public void win() { m_strategy.study(true); m_winCount++; m_gameCount++; } public void lose() { m_strategy.study(false); m_loseCount++; m_gameCount++; } public void even() { m_gameCount++; } @Override public String toString() { return "{" + m_name + ":" + m_gameCount + "games, " + m_winCount + "win " + m_loseCount + "lose" + "]"; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class Main { public static void main(String[] args) { final Player player1 = new Player("Tanaka", new FoolStrategy(1)); final Player player2 = new Player("Yoshida", new SmartStrategy(2)); for(int i = 0; i < 10 ; i++) { final Hand nextHand1 = player1.nextHand(); final Hand nextHand2 = player2.nextHand(); if(nextHand1.isStrongerThan(nextHand2)) { String.format("Winner:%s", player1); player1.win(); player2.lose(); } else if(nextHand2.isStrongerThan(nextHand1)) { String.format("Winner:%s", player2); player1.lose(); player2.win(); } else { player1.even(); player2.even(); } } System.out.println("Total result:"); System.out.println(player1); System.out.println(player2); } } |
1 2 3 |
Total result: {Tanaka:10games, 2win 4lose] {Yoshida:10games, 4win 2lose] |
メリットは以下の二つでした。
・アルゴリズムを容易に切り替えることができる
・実行中にアルゴリズムを切り替えることもできる
Mainクラスを見てみてください。
実行中に簡単にアルゴリズムを切り替えられているのがわかります。