Help us understand the problem. What is going on with this article?

Strategyパターンを触ってみる

今日の目標

JavaのStrategyパターンとはどういったものなのかを知る

使うもの

ではスタート

はじめに

まずはStrategyパターンとはどのようなものなのかをWikipediaさんで調べてみる。

Strategyパターンは、コンピュータープログラミングの領域において、アルゴリズムを実行時に選択することができるデザインパターンである。(wikipedia-Strategyパターン

アルゴリズムのパターンをある程度用意しておいて、動的に切り替えられるようにするパターンのことかな。何となくわかるような。

本のサンプルを参考に自分で作ってみる

今回張り切ってサンプルを参考に自分で作ってみました。が、サンプルのほうがわかりやすいと思うので、わからなかったら本のほうを参考にしてください…。(本のサンプルはじゃんけんゲームです)

今回作るのは、テレビゲームのカジノとかでよく見かけるアップダウンゲームです。アップダウンゲームとは、トランプを一枚表にし、次にめくるカードがそのカードより「上か、下か」を当てる簡単なゲームです。これをStrategyパターンを使って作りました。
※本来ならトランプはA~Kまでですが、今回面倒だったので1~13という数字になってます。また、AはKより強いのですが、数字にした関係でAに当たる1は一番弱くなってます。ご了承ください

Strategyインタフェースは、shoutメソッドだけ定義してます。内部の実装は各実装クラスにお任せして、とりあえず「上だ!」「下だ!」ってことを叫んでね、ということ。

Strategy.java
package strategy;

import util.ShoutWord;

public interface Strategy {

    // 受け取ったnumberによってUPとDOWNどちらを宣言するか
    public abstract ShoutWord shout(int number);

}

1つ目のStrategy実装はこんな感じ。単純。

  • 1~6が出た場合⇒次のトランプはそれよりも上であると判断する
  • 7~13が出た場合⇒次のトランプはそれよりも下であると判断する
HalfStrategy.java
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が出た場合⇒次の数字をランダムで予測して、予測した数字のほうが大きかったら上であると判断して、予測した数字のほうが小さかったら下であると判断する
OneThirdStrategy.java
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!

Player.java
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。

ShoutWord.java
package util;

public enum ShoutWord {
    UP,
    DOWN
}
ResultWord.java
package util;

public enum ResultWord {
    OK,
    NG
}

実行するMainクラス。1000回勝負したうち、勝った回数と負けた回数を出力してます。
途中で、もし結果が「負けが多い」ということであれば(Playerのresultメソッド呼び出し)、別の戦略に乗り換えるようにしてます。OneThirdStrategyからHalfStrategyへ。(ロジックはコピペしてます。ホントはいけないんですが、今回はご容赦を)

Main.java
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実装を変えれば良いのですね。

今日はこんな感じで!

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away