php设计模式-策略模式-例题学习
本文改自《设计模式-java语言中的应用》中的策略模式章节。作者:结城 浩(日本)简单的说,策略模式就是算法替换,用不同的类实现不同的算法。
难点:需要根据算法设计出不同的方法,参数等。
程序示例
这里的程序是计算机游戏“剪刀石头布”。
猜拳时的策略有两种方法。第一种方法有点笨,“猜赢之后继续出同样的招式”(WinningStrategy),第2种方法则是“从上一次出的招式,以概率分配方式求出下一个招式的几率”(ProbStrategy)。
类一览表。
Hand:表示猜拳手势的类
HandStrategyInterface:表示猜拳战略的接口
Player:表示玩猜拳的游戏者的类
WinningStratrgy:策略1,表示猜赢之后继续出同样招式的战略的类
ProbStratrgy:策略2,表示根据上一次出的招式以概率计算出下一个招式的类。
Main:执行主类
代码在附件。
Hand.php
<?php
/**
* 手势类
*
* @author 结城 浩
*/
class Hand
{
public static $HANDVALUE_GUU = 0; //表示石头的值
public static $HANDVALUE_CHO = 1; //表示剪刀的值
public static $HANDVALUE_PAA = 2; //表示布的值
private static $name_arr = array('石头', '剪刀', '布');
/**
* 手势的值
*
* @var int
*/
private $handValue;
/**
* 私有构造方法
*
* @param int $handvalue 手势的值
*/
private function __construct($handvalue)
{
$this->handValue = $handvalue;
}
/**
* 该方法实现单例数组,里面存放3个对象
*
* @return array 对象数组
*/
public static function getHandArr()
{
static $hand_arr = array();
if (!$hand_arr) {
$hand_arr = array(
new Hand(self::$HANDVALUE_GUU),
new Hand(self::$HANDVALUE_CHO),
new Hand(self::$HANDVALUE_PAA),
);
}
return $hand_arr;
}
/**
* 外部调用,获取一个手势对象
*
* @param int $handvalue 手势的值
* @return Hand 一个手势对象
*/
public static function getHand($handvalue)
{
$hand = self::getHandArr();
return $hand[$handvalue];
}
/**
* 判断是否战胜另一个手势
*
* @param Hand $h 另一个手势对象
* @param boolean
*/
public function isStrongerThan(Hand $h)
{
return $this->fight($h) == 1;
}
/**
* 判断是否输给另一个手势
*
* @param Hand $h 另一个手势对象
* @param boolean
*/
public function isWeakerThan(Hand $h)
{
return $this->fight($n) == -1;
}
/**
* 与另一个手势判断输赢
*
* @param Hand $h 另一个手势对象
* @return int 胜为1 ,负为-1,平局为0
*/
private function fight(Hand $h)
{
if ($this->handValue == $h->handValue) {
return 0;
} elseif (($this->handValue + 1) % 3 == $h->handValue) {
return 1;
} else {
return -1;
}
}
/**
* 字符串显示
*/
function __toString()
{
return self::$name_arr[$this->handValue];
}
}
HandStrategyInterface.php
<?php
/**
* 手势策略接口
*/
interface HandStrategyInterface
{
/**
* 得到下一个手势
*/
public function nextHand();
/**
* 策略的智能学习
*/
public function study($win);
}
Player.php
<?php
require_once 'HandStrategyInterface.php';
/**
* 游戏者类
*
* @author 结城 浩
*/
class Player
{
/**
* 游戏者名称
*
* @var string
*/
private $name;
/**
* 私有策略对象
*
* @var HandStrategyInterface
*/
private $strategy;
/**
* 总胜利次数
*
* @var int
*/
private $wincount;
/**
* 总失败次数
*
* @var int
*/
private $losecount;
/**
* 总游戏次数
*
* @var int
*/
private $gamecount;
/**
* 构造方法
*
* @param string $name 游戏者名称
* @param HandStrategyInterface $strategy 策略对象
*/
function __construct($name, HandStrategyInterface $strategy)
{
$this->name = $name;
$this->strategy = $strategy;
$this->wincount = $this->losecount = $this->gamecount = 0;
}
/**
* 根据策略返回游戏者的下一个手势
*
* @return Hand 游戏者的下一个手势
*/
public function nextHand()
{
$hand = $this->strategy->nextHand();
echo $this->name . ":" . $hand ."<br>";
return $hand;
}
/**
* 胜利后的处理
*/
public function win()
{
$this->strategy->study(true);
$this->wincount++;
$this->gamecount++;
}
/**
* 失败后的处理
*/
public function lose()
{
$this->strategy->study(false);
$this->losecount++;
$this->gamecount++;
}
/**
* 平局后的处理
*/
public function even()
{
$this->gamecount++;
}
/**
* 魔术方法显示游戏者信息
*
* @return string
*/
function __toString()
{
return '[' . $this->name . ':' . $this->gamecount . ' games, ' .
$this->wincount . ' win, ' . $this->losecount . ' lose]';
}
}
WinningStrategy.php
<?php
require_once 'Hand.php';
require_once 'HandStrategyInterface.php';
/**
* 简单策略类
*
* 这是第一个策略,非常简单,如果赢了,就继续同一个手势,如果输了,随机出手势。
*
* @author 结城 浩
*/
class WinningStrategy implements HandStrategyInterface
{
/**
* 与该策略算法有关的变量
*
* @var boolean
*/
private $won = false;
/**
* 上一个手势
*
* @var Hand
*/
private $prevhand;
public function __construct()
{
}
/**
* 策略的算法核心。
*
* 如果赢,就继续相同的手势,输则随机产生一个。
*
* @return Hand
*/
public function nextHand()
{
if (!$this->won) {
$this->prevhand = Hand::getHand(mt_rand(0, 2));
}
return $this->prevhand;
}
/**
* 学习方法,只记住当前的输赢结果
*
* @param boolean 输赢结果
*/
public function study($win)
{
$this->won = $win;
}
}
ProbStrategy.php
<?php
require_once 'Hand.php';
require_once 'HandStrategyInterface.php';
/**
* 复杂策略类
*
* 这是第二个策略,虽然下一次的手势都是由随机数决定,不过它会参考之前的输赢记录,机动性的更改手势的出现几率。
* history字段是一份列出过去输赢记录的表格,用来计算几率。history是一个int类型的2维数组,各下标的含义是
* history[上一次的手势][下一次的手势]
* 这个表达式的值越大,则代表之前获胜的几率越高。用实际的数据可能会更清楚一点。
* 这个作战策略的前提是对手的出拳方式也有另一种Pattern。
*
* @author 结城 浩
*/
class ProbStrategy implements HandStrategyInterface
{
/**
* 前面手势的值,统计用
*
* @var int
*/
private $prevHandValue = 0;
/**
* 后面手势的值,统计用
*
* @var int
*/
private $currentHandValue = 0;
/**
* 存放历史数据的数组
*
* history 石头之后出石头的不败(获胜或平手)次数。
* history 石头之后出剪刀的不败(获胜或平手)次数。
* history 石头之后出布的不败(获胜或平手)次数。
* history 剪刀之后出石头的不败(获胜或平手)次数。
* history 剪刀之后出剪刀的不败(获胜或平手)次数。
* history 剪刀之后出布的不败(获胜或平手)次数。
* history 布之后出石头的不败(获胜或平手)次数。
* history 布之后出剪刀的不败(获胜或平手)次数。
* history 布之后出布的不败(获胜或平手)次数。
*
* @var array 二维数组
*/
private $history = array(
array(1, 1, 1),
array(1, 1, 1),
array(1, 1, 1),
);
public function __construct()
{
}
/**
* 算法核心
*
* 根据历史数据,计算出某一个手势的下一个手势的3个概率(比如6:4:13),然后
* 也不一定就是那个最大概率的手势,而是依然根据概率得到下一个手势。
*/
public function nextHand()
{
$bet = mt_rand(0, $this->getSum($this->currentHandValue) - 1);
$handvalue = 0;
if ($bet < $this->history[$this->currentHandValue]) {
$handvalue = 0;
} elseif ($bet < ($this->history[$this->currentHandValue] +
$this->history[$this->currentHandValue]) ) {
$handvalue = 1;
} else {
$handvalue = 2;
}
$this->prevHandValue = $this->currentHandValue;
$this->currentHandValue = $handvalue;
$hand = Hand::getHand($handvalue);
return $hand;
}
/**
* 从历史数据中得到某一个手势的次数之和,算概率用的
*
* @param int $hv
* @return int
*/
private function getSum($hv)
{
$sum = 0;
for ($i = 0; $i < 3; $i++) {
$sum += $this->history[$hv][$i];
}
return $sum;
}
/**
* 该策略的学习就是把结果记入统计数据里。
*
* @param boolean $win 本次的输赢结果
*/
public function study($win)
{
if ($win) {
$this->history[$this->prevHandValue][$this->currentHandValue]++;
} else {
$this->history[$this->prevHandValue][($this->currentHandValue + 1) % 3]++;
$this->history[$this->prevHandValue][($this->currentHandValue + 2) % 3]++;
}
}
}
Main.php
<?php
require_once 'Hand.php';
require_once 'WinningStrategy.php';
require_once 'ProbStrategy.php';
require_once 'Player.php';
/**
* 执行主类
*
* 使用方法,把这几个类一起拷贝到web服务器的docment_root路径下,然后
* http://localhost/Main.php
*
* @author 结城 浩
*/
class Main
{
public static function run()
{
//得到两个游戏者,策略不同
$player1 = new Player('张三', new WinningStrategy());
$player2 = new Player('李四', new ProbStrategy());
//1000次运算,得到结果,并不停的输出。
for ($i = 0; $i < 1000; $i++) {
$nextHand1 = $player1->nextHand();
$nextHand2 = $player2->nextHand();
if ($nextHand1->isStrongerThan($nextHand2)) {
$player1->win();
$player2->lose();
self::echoinfo('胜利者: ' . $player1);
} elseif ($nextHand2->isStrongerThan($nextHand1)) {
$player1->lose();
$player2->win();
self::echoinfo('胜利者: ' . $player2);
} else {
$player1->even();
$player2->even();
self::echoinfo('平手。 ');
}
self::echoinfo(' ');
}
//统计
self::echoinfo('======================= 低调滴分割线=======================');
self::echoinfo($player1);
self::echoinfo($player2);
}
/**
* 只是一个便利的输出换行
*
* @paramstring $s 待输出信息
* @return string 加了换行后的输出
*/
public static function echoinfo($s)
{
echo $s . "<br />\n";
}
}
//执行
Main::run();
运行结果示例:
代码在附件。
页:
[1]