デザインパターン入門 | Strategy(ストラテジー)パターン

「Strategy(ストラテジー)」パターンは、ある対象に対して「Strategy(戦略)」を作り、「戦略の入れ替え」ができるデザインパターンです。

「ある対象」には、主に「勝ち負け」があるような「勝負事」を想定しますが、どの戦略が有効なのかを調べるための「シミュレーション」などでも使われます。

今回は「プロジェクト」をテーマに「Strategy(ストラテジー)」パターンについて見ていきたいと思います。

「Strategy」インターフェースの役割と「戦略」の作り方

「ゲーム」や「シミュレーション」など、「競争・対戦」に必要なものが「戦略」です。

「戦略」は状況によって変わることもありますし、プレイヤーによって変わることもあります。

ということは「戦略」は「交換・変化が可能な仕組み」を持っていることが求められる部分でもあります。

今回作成するプログラムでは、「Strategy」インターフェースとして「戦略のひな型」を作っています。

「プロジェクトの成功可否を競う」ことを想定すると、「Strategy」インターフェースは下記のようになります。(プログラム言語:Java)

public interface Strategy {
	public boolean judgeProject(Project pj); // プロジェクトの実行可否判断
}

このインターフェースには「judgeProject」抽象メソッドが定義されていますが、それぞれの「戦略」を表すクラスはこのインターフェースを実装しています。

「judgeProject」抽象メソッドは、「プロジェクトの実行可否」を判断する役割を持っていますが、どのような基準で判断するのかは「戦略」によって異なるため、それぞれの「戦略」を表すクラスによって定義していきます。

今回は「仕事のプロジェクト」をテーマにしていきたいと思いますが、「プロジェクトの実行可否」を決定する「経営者(社長)」を表すクラスも必要となってきます。

このそれぞれのクラスの関係を表すと下図のようになります。

ストラテジー1

「President」クラスは「経営者・社長」を表すクラスです。

この「President」クラスでは、「Strategy」インターフェース型のフィールドを持っているため、「has a」の関係となっています。

「戦略」にはさまざまなものがありますが、今回は「強気な戦略(Strong Strategy)」と「慎重な戦略(Careful Strategy)」の2つの戦略を作ってみたいと思います。

この2つの戦略は「Strategy」インターフェースを実装しています。

ストラテジー2

「President」クラスは下記のようになります。

public class President {
	private String name; // 氏名
	private int money = 1000000; // 保有金額
	private Strategy strategy;

	// コンストラクタ
	public President(String name, Strategy strategy) {
		this.name = name;
		this.strategy = strategy;
	}

	// プロジェクトを実行するかどうかを判定
	public boolean judgeProject(Project pj) {
		return this.strategy.judgeProject(pj);

	}

	// プロジェクトリストを実行
	public void execProjectList(ArrayList projects) {
		for ( Project project : projects ) {
			System.out.print(project);
			if ( this.judgeProject(project) ) {
				System.out.print("プロジェクト実行  プロジェクト実行結果:");
				if( this.execProject(project) ) {
					System.out.println("成功");
				} else {
					System.out.println("失敗");
				}
			} else {
				System.out.println("プロジェクト未実行");
			}
		}

	}

	// プロジェクトを実行
	public boolean execProject(Project pj) {
		// コスト減算
		this.money -= ( pj.getPrice() / 2 );

		if( pj.execProject() == true ) {
			this.money += pj.getPrice();
			return true;
		}
		return false;
	}

	public String getName() {
		return name;
	}

	public int getMoney() {
		return money;
	}

	@Override
	public String toString() {
		return "経営者:" + this.getName() + " 保有金額:" + NumberFormat.getNumberInstance().format(this.getMoney());
	}
}

「StrongStrategy」クラスは下記のようになります。

public class StrongStrategy implements Strategy {
	// プロジェクトの実行可否を判断
	@Override
	public boolean judgeProject(Project pj) {
	 pj.getProbabilityOfSuccess();
		if ( pj.getProbabilityOfSuccess() > 30 ) {
			return true;
		}
		return false;
	}
}

「CarefulStrategy」クラスは下記のようになります。

public class CarefulStrategy implements Strategy {
	// プロジェクトの実行可否を判断
	@Override
	public boolean judgeProject(Project pj) {
	 pj.getProbabilityOfSuccess();
		if ( pj.getProbabilityOfSuccess() > 50 && pj.getPrice() > 5000000) {
			return true;
		}
		return false;
	}
}

この2つの「戦略クラス」の違いは「プロジェクトの実行可否の基準」なので、それぞれの「戦略クラス」で実行可否を判断するための「judgeProject」メソッドが定義されています。

といっても判断基準は「プロジェクトの成功確率」と「プロジェクトの成功報酬金額」です。

「強気な戦略」では、「プロジェクトの成功確率」が30%を上回っていれば、プロジェクトを実行します。

一方、「慎重な戦略」では、「プロジェクトの成功確率」が50%を上回っていて、「プロジェクトの成功報酬金額」が「5,000,000円」を上回るプロジェクトのみを実行します。

「Project」クラスの概要

「Project」クラスは文字通り「プロジェクト」自体を表すクラスです。

public class Project {
	private int probabilityOfSuccess; // 成功確率
	private int price; // 成功報酬金額

	// コンストラクタ
	public Project ( int probabilityOfSuccess, int price) {
		this.probabilityOfSuccess = probabilityOfSuccess;
		this.price = price;
	}

	// プロジェクトを実行
	public boolean execProject() {
		int rand = new Random().nextInt(100) + 1;

		if ( rand <= this.getProbabilityOfSuccess()) {
			return true;
		}
		return false;
	}

	public int getProbabilityOfSuccess() {
		return probabilityOfSuccess;
	}

	public int getPrice() {
		return price;
	}

	@Override
	public String toString() {
		return "成功確率:" + String.format("%02d", this.getProbabilityOfSuccess()) + "%  成功報酬金額:" + NumberFormat.getNumberInstance().format(this.getPrice()) + "円";
	}
}

「プロジェクトの成功確率」「プロジェクトの成功報酬金額」を表すフィールドや「プロジェクトの実行」を行うメソッドが定義されていますね。

「プロジェクトの実行」といっても、今回は「乱数値」を元にプロジェクトの成否を決定しているだけです。

プロジェクトを管理するための「ProjectManager」クラスは下記のようになります。

public class ProjectManager {
	private ArrayList projects = new ArrayList(); // プロジェクトリスト
	private final int UPPER_LIMIT = 9000000; // プロジェクト金額の上限設定値
	private final int LOWER_LIMIT = 1000000; // プロジェクト金額の下限設定値

	// プロジェクトリストを作成
	public void createProjectList(int numOfProjects) {
		for ( int i = 0; i < numOfProjects; i++) {
			projects.add(createProject());
		}
	}

	// プロジェクトを作成
	private Project createProject() {
		Random rand = new Random();
		int probabilityOfSuccess = rand.nextInt(100) + 1;
		int price= (rand.nextInt(((UPPER_LIMIT/1000)+1)) * 1000) + LOWER_LIMIT;

		return new Project(probabilityOfSuccess, price);
	}

	// プロジェクトリストを表示
	public void displayProjectsList() {
		for ( Project pj : projects) {
			System.out.println(pj);
		}
		System.out.println();
	}

	// 作成したプロジェクトの取得
	public ArrayList getProjects() {
		return this.projects;
	}
}

1つ1つのプロジェクトは「ProjectManager」クラスによって「ArrayList」で管理されています。

このクラスには「プロジェクトリストの取得・表示・作成」などのメソッドが定義されています。

これで必要なクラスは全て揃いましたので、「プログラムの実行例」について見ていきたいと思います。

プログラムの実行

プログラムを実行するための「main」」メソッドは下記のようになります。

public class Main {
	public static void main(String[] args) {
		// プロジェクト管理者を作成
		ProjectManager pjMan = new ProjectManager();

		// 経営者の作成
		President presidentA = new President("佐藤" , new StrongStrategy());
		President presidentB = new President("西山" , new CarefulStrategy());

		// プロジェクトの作成
		pjMan.createProjectList(10);

		// PresidentA
		System.out.println("■プロジェクト実行前 " + presidentA);
		presidentA.execProjectList(pjMan.getProjects());
		System.out.println("■プロジェクト実行後 " + presidentA);

		System.out.println();
		System.out.println("===================================================================================");
		System.out.println();

		// PresidentB
		System.out.println("■プロジェクト実行前 " + presidentB);
		presidentB.execProjectList(pjMan.getProjects());
		System.out.println("■プロジェクト実行後 " + presidentB);
	}
}

「佐藤」さんと「西山」さんという2人の経営者が登場していますが、「佐藤社長」は「30%」という低い成功率であっても「プロジェクトを実行」しているため「強気戦略」を推進しています。

一方、「西山社長」は「慎重」派なので、「50%を超える成功率」そして、「プロジェクト報酬金額」が「5,000,000円」を超えるプロジェクトしか実行しません。

プログラムを実行すると「コスト」としてプロジェクト報酬金額の50%が保有金額から差し引かれますので、「プロジェクトの実行」に失敗すると「保有金額が減少していく仕組み」となっています。

実行結果は下記のようになりました。

■プロジェクト実行前 経営者:佐藤 保有金額:1,000,000
成功確率:31%  成功報酬金額:6,728,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:32%  成功報酬金額:9,917,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:13%  成功報酬金額:4,843,000円プロジェクト未実行
成功確率:89%  成功報酬金額:5,710,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:32%  成功報酬金額:9,575,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:67%  成功報酬金額:1,169,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:81%  成功報酬金額:2,144,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:12%  成功報酬金額:2,752,000円プロジェクト未実行
成功確率:57%  成功報酬金額:8,070,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:80%  成功報酬金額:3,397,000円プロジェクト実行  プロジェクト実行結果:成功
■プロジェクト実行後 経営者:佐藤 保有金額:-18,000

===================================================================================

■プロジェクト実行前 経営者:西山 保有金額:1,000,000
成功確率:31%  成功報酬金額:6,728,000円プロジェクト未実行
成功確率:32%  成功報酬金額:9,917,000円プロジェクト未実行
成功確率:13%  成功報酬金額:4,843,000円プロジェクト未実行
成功確率:89%  成功報酬金額:5,710,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:32%  成功報酬金額:9,575,000円プロジェクト未実行
成功確率:67%  成功報酬金額:1,169,000円プロジェクト未実行
成功確率:81%  成功報酬金額:2,144,000円プロジェクト未実行
成功確率:12%  成功報酬金額:2,752,000円プロジェクト未実行
成功確率:57%  成功報酬金額:8,070,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:80%  成功報酬金額:3,397,000円プロジェクト未実行
■プロジェクト実行後 経営者:西山 保有金額:7,890,000

「強気な戦略」の「佐藤」さんのプロジェクトの実行結果は「-18.000円」となりましたが、「慎重な戦略」の「西山」さんのプロジェクトの実行結果は「7,890,000円」となりました。

しかし、この結果は「乱数値」を元にしているため、毎回実行結果は異なります。

試しにもう一回実行してみると、

■プロジェクト実行前 経営者:佐藤 保有金額:1,000,000
成功確率:53%  成功報酬金額:6,766,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:55%  成功報酬金額:8,325,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:75%  成功報酬金額:7,019,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:96%  成功報酬金額:9,505,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:13%  成功報酬金額:9,334,000円プロジェクト未実行
成功確率:11%  成功報酬金額:5,381,000円プロジェクト未実行
成功確率:52%  成功報酬金額:7,658,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:12%  成功報酬金額:2,283,000円プロジェクト未実行
成功確率:85%  成功報酬金額:9,137,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:36%  成功報酬金額:5,147,000円プロジェクト実行  プロジェクト実行結果:成功
■プロジェクト実行後 経営者:佐藤 保有金額:27,778,500

===================================================================================

■プロジェクト実行前 経営者:西山 保有金額:1,000,000
成功確率:53%  成功報酬金額:6,766,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:55%  成功報酬金額:8,325,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:75%  成功報酬金額:7,019,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:96%  成功報酬金額:9,505,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:13%  成功報酬金額:9,334,000円プロジェクト未実行
成功確率:11%  成功報酬金額:5,381,000円プロジェクト未実行
成功確率:52%  成功報酬金額:7,658,000円プロジェクト実行  プロジェクト実行結果:成功
成功確率:12%  成功報酬金額:2,283,000円プロジェクト未実行
成功確率:85%  成功報酬金額:9,137,000円プロジェクト実行  プロジェクト実行結果:失敗
成功確率:36%  成功報酬金額:5,147,000円プロジェクト未実行
■プロジェクト実行後 経営者:西山 保有金額:2,283,000

「強気な戦略」の「佐藤」さんのプロジェクトの実行結果は「27,778,500円」となりましたが、「慎重な戦略」の「西山」さんのプロジェクトの実行結果は「2,283,000円」となりました。

このように「乱数値」を元にシミュレーションをすることを「モンテカルロ法」と言いますが、統計的に結果を導き出すことができるようになります。

「Strategy(ストラテジー)」パターンは、さまざまな戦略を変えることができるため、いろいろな戦略を作り、どの戦略が効果的なのかを「シミュレーション」することができます。

ぜひ何か身近な「シミュレーション対象」を見つけて、「Strategy(ストラテジー)」パターンを利用したプログラムを作ってみてください。

HOMEへ