下面以糖果机为例进行讲解。
分析&设计
糖果机的状态可分为四种: 没有硬币、有硬币、糖果售出、糖果售馨。
糖果机可执行的动作也可分为四种:插入硬币、弹出硬币、转动曲柄、发放糖果。
糖果机状态图如下:
相应地,其类图如下 (接口中所含四种方法已省略):
所需接口:状态。
所需类有:糖果机,售出状态,售馨状态,无硬币状态,有硬币状态。
实现
1. 状态接口
interface State
{
public function insertCoin();
public function ejectCoin();
public function turnCrank();
public function dispense();
}
2. 糖果机类
class GumballMachine
{
private $soldOutState;
private $noCoinState;
private $hasCoinState;
private $soldState;
private $state;
private $count = 0;
public function __construct ( $numGumballs = 0 ) {
$this->soldOutState = new SoldOutState($this);
$this->noCoinState = new NoCoinState($this);
$this->hasCoinState = new HasCoinState($this);
$this->soldState = new SoldState($this);
$this->count = $numGumballs;
if ( $numGumballs > 0 ) {
$this->state = $this->noCoinState;
} else {
$this->state = $this->soldOutState;
}
}
public function insertCoin () {
$this->state->insertCoin();
}
public function ejectCoin () {
$this->state->ejectCoin();
}
public function turnCrank () {
$this->state->turnCrank();
$this->state->dispense();
}
public function setState ( $state ) {
$this->state = $state;
}
public function releaseBall () {
echo "还有糖果{$this->count}. 将有糖果滚出...\n";
if ( $this->count > 0 ) {
$this->count = $this->count - 1;
}
}
public function getNoCoinState () {
return $this->noCoinState;
}
public function getHasCoinState () {
return $this->hasCoinState;
}
public function getSoldOutState () {
return $this->soldOutState;
}
public function getSoldState () {
return $this->soldState;
}
public function getCount () {
return $this->count;
}
}
3. 售出状态类
class SoldState implements State
{
private $machine;
public function __construct ( $machine ) {
$this->machine = $machine;
}
public function insertCoin () {
echo "请稍等, 我们已经给了你一个糖果.\n";
}
public function ejectCoin () {
echo "抱歉, 你已经转动了曲柄.\n";
}
public function turnCrank () {
echo "转两次也不会给你两个糖果.\n";
}
public function dispense () {
$this->machine->releaseBall();
if ( $this->machine->getCount() > 0 ) {
$this->machine->setState( $this->machine->getNoCoinState() );
} else {
echo "没有糖果了.\n";
$this->machine->setState( $this->machine->getSoldOutState() );
}
}
}
4. 售馨状态类
class SoldOutState implements State
{
private $machine;
public function __construct ( $machine ) {
$this->machine = $machine;
}
public function insertCoin () {
echo "糖果已经没了. 别插硬币了.\n";
}
public function ejectCoin () {
echo "你还没插入硬币呢.\n";
}
public function turnCrank () {
echo "没有糖果了. 转动曲柄也没用.\n";
}
public function dispense () {
echo "没有糖果可发放\n";
}
}
5. 无硬币状态类
class NoCoinState implements State
{
private $machine;
public function __construct ( $machine ) {
$this->machine = $machine;
}
public function insertCoin () {
echo "你插入了硬币.\n";
$this->machine->setState( $this->machine->getHasCoinState() );
}
public function ejectCoin () {
echo "你还没插硬币呢.\n";
}
public function turnCrank () {
echo "你转动了曲柄, 但没有硬币.\n";
}
public function dispense () {
echo "你得先插入硬币.\n";
}
}
6. 有硬币状态类
class HasCoinState implements State
{
private $machine;
public function __construct ( $machine ) {
$this->machine = $machine;
}
public function insertCoin () {
echo "已经有硬币了. 别再投了.\n";
}
public function ejectCoin () {
echo "硬币已弹出.\n";
$this->machine->setState( $this->machine->getNoCoinState() );
}
public function turnCrank () {
echo "你转动了曲柄.\n";
$this->machine->setState( $this->machine->getSoldState() );
}
public function dispense () {
echo '没有糖果弹出.';
}
}
测试用例
$m = new GumballMachine(7);
$m->insertCoin();
$m->turnCrank();
$m->ejectCoin();
echo '剩余糖果: '. $m->getCount() ."\n\n";
$m->insertCoin();
$m->turnCrank();
echo '剩余糖果: '. $m->getCount() ."\n\n";
$m->insertCoin();
$m->ejectCoin();
$m->turnCrank();
echo '剩余糖果: '. $m->getCount() ."\n\n";
$m->insertCoin();
$m->turnCrank();
echo '剩余糖果: '. $m->getCount() ."\n\n";
$m->insertCoin();
$m->insertCoin();
$m->turnCrank();
echo '剩余糖果: '. $m->getCount() ."\n";
测试结果
你插入了硬币.
你转动了曲柄.
将有糖果滚出...
你还没插硬币呢.
剩余糖果: 6
你插入了硬币.
你转动了曲柄.
将有糖果滚出...
剩余糖果: 5
你插入了硬币.
硬币已弹出.
你转动了曲柄, 但没有硬币.
你得先插入硬币.
剩余糖果: 5
你插入了硬币.
你转动了曲柄.
将有糖果滚出...
剩余糖果: 4
你插入了硬币.
已经有硬币了. 别再投了.
你转动了曲柄.
将有糖果滚出...
剩余糖果: 3