设计模式之: 状态模式

什么是状态设计模式

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

什么时候使用状态模式

对象中频繁改变非常依赖于条件语句。 就其自身来说, 条件语句本身没有什么问题(如switch语句或带else子句的语句),不过, 如果选项太多, 以到程序开始出现混乱, 或者增加或改变选项需要花费太多时间, 甚至成为一种负担, 这就出现了问题
对于状态设计模式, 每个状态都有自己的具体类, 它们实现一个公共接口. 我们不用查看对象的控制流, 而是从另一个角度来考虑, 即对象的状态.
 
状态机是一个模型, 其重点包括不同的状态, 一个状态到另一个状态的变迁, 以及导致状态改变的触发器.
以开灯关灯为例子, 状态模型的本质分为3点:
  • 状态(关灯和开灯)
  • 变迁(从关灯到开灯, 以及从开灯到关灯)
  • 触发器(灯开关)
所以状态模式都需要一个参与者来跟踪对象所处的状态. 以Light为例, Light需要知道当前状态是什么. 

示例:开灯关灯

Light.php
offState = new OffState($this);
        $this->onState = new OnState($this);
        //开始状态为关闭状态Off
        $this->currentState = $this->offState;
    }
    //调用状态方法触发器
    public function turnLightOn()
    {
        $this->currentState->turnLightOn();
    }
    public function turnLightOff()
    {
        $this->currentState->turnLightOff();
    }
    //设置当前状态
    public function setState(IState $state)
    {
        $this->currentState = $state;
    }
    //获取状态
    public function getOnState()
    {
        return $this->onState;
    }
    public function getOffState()
    {
        return $this->offState;
    }
}
在构造函数中, Light实例化IState实现的两个实例-----一个对应关, 一个对应开
 $this->offState = new OffState($this);
$this->onState = new OnState($this);
这个实例化过程用到了一种递归, 称为自引用(self-referral)
构造函数参数中的实参写为$this, 这是Light类自身的一个引用. 状态类希望接收一个Light类实例做参数,.
setState方法是为了设置一个当前状态 需要一个状态对象作为实参, 一旦触发一个状态, 这个状态就会向Light类发送信息, 指定当前状态.

状态实例

IState接口
IState.php
 
   
该接口的实现类
OnState.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "灯已经打开了->不做操作
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState()); } }
OffState.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "灯打开!可以看见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOnState()); } public function turnLightOff() { echo "灯已经关闭了->不做操作
"; } }
默认状态是OffState, 它必须实现IState方法turnLightOn和turnLightOff, Light调用turnLightOn方法, 会显示(灯打开!可以看见帅哥chenqionghe了), 然后将OnState设置为当前状态, 不过,如果是调用 OffState的turnLightOff方法, 就只有提示灯已经被关闭了 不会有其他动作.

客户

Client的所有请求都是通过Light发出, Client和任何状态类之间都没有直接连接, 包括IState接口.下面的Client显示了触发两个状态中所有方法的请求.
Client.php
light = new Light();
        $this->light->turnLightOn();
        $this->light->turnLightOn();
        $this->light->turnLightOff();
        $this->light->turnLightOff();
    }
}
$worker = new Client();

增加状态

对于所有的设计模式来说,很重要的一个方面是: 利用这些设计模式可以很容易地做出修改. 与其他模式一样,状态模式也很易于更新和改变. 下面在这个灯的示例上再加两个状态:更亮(Brighter)和最亮(Brightest)
现在变成了4个状态, 序列有所改变. '关'(off)状态只能变到"开"(on)状态, on状态不能变到off状态. on状态只能变到"更亮"(brighter)状态和"最亮"(brightest)状态. 只能最亮状态才可能变到关状态.

改变接口

要改变的第一个参与者是接口IState, 这个接口中必须指定相应的方法, 可以用来迁移到brighter和brightest状态.
IState.php
 
   
现在所有状态类都必须包含这4个方法, 它们都需要结合到Light类中.
 

改变状态

状态设计模式中有改变时, 这些新增的改变会对模式整体的其他方面带来影响. 不过, 增加改变相当简单, 每个状态只有一个特定的变迁.
四个状态
OnState.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "不合法的操作!
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState()); } public function turnBrighter() { echo "灯更亮了, 看帅哥chenqionghe看得更真切了!
"; $this->light->setState($this->light->getBrighterState()); } public function turnBrightest() { echo "不合法的操作!
"; } }
OffState.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "灯打开!可以看见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOnState()); } public function turnLightOff() { echo "不合法的操作!
"; } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "不合法的操作!
"; } }
Brighter.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "不合法的操作!
"; } public function turnLightOff() { echo "不合法的操作!
"; } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "灯最亮了, 看帅哥chenqionghe已经帅到无敌!
"; $this->light->setState($this->light->getBrightestState()); } }
Brightest.php
light = $light;
    }
    public function turnLightOn()
    {
        echo "灯已经打开了->不做操作
"; } public function turnLightOff() { echo "灯关闭!看不见帅哥chenqionghe了!
"; $this->light->setState($this->light->getOffState()); } public function turnBrighter() { echo "不合法的操作!
"; } public function turnBrightest() { echo "不合法的操作!
"; } }

更新Light类

Light.php
offState = new OffState($this);
        $this->onState = new OnState($this);
        $this->brighterState = new BrighterState($this);
        $this->brightestState = new BrightestState($this);
        //开始状态为关闭状态Off
        $this->currentState = $this->offState;
    }
    //调用状态方法触发器
    public function turnLightOn()
    {
        $this->currentState->turnLightOn();
    }
    public function turnLightOff()
    {
        $this->currentState->turnLightOff();
    }
    public function turnLightBrighter()
    {
        $this->currentState->turnBrighter();
    }
    public function turnLigthBrightest()
    {
        $this->currentState->turnBrightest();
    }
    //设置当前状态
    public function setState(IState $state)
    {
        $this->currentState = $state;
    }
    //获取状态
    public function getOnState()
    {
        return $this->onState;
    }
    public function getOffState()
    {
        return $this->offState;
    }
    public function getBrighterState()
    {
        return $this->brighterState;
    }
    public function getBrightestState()
    {
        return $this->brightestState;
    }
}
更新客户
light = new Light();
        $this->light->turnLightOn();
        $this->light->turnLightBrighter();
        $this->light->turnLigthBrightest();
        $this->light->turnLightOff();
        $this->light->turnLigthBrightest();
    }
}
$worker = new Client();
运行结果如下
灯打开!可以看见帅哥chenqionghe了!
灯更亮了, 看帅哥chenqionghe看得更真切了!
灯最亮了, 看帅哥chenqionghe已经帅到无敌!
灯关闭!看不见帅哥chenqionghe了!
不合法的操作!

 

九宫格移动示例

九宫格的移动分为4个移动:
  • 上(Up)
  • 下(Down)
  • 左(Left)
  • 右(Right)
对于这些移动,规则是要求单元格之间不能沿对角线方向移动. 另外, 从一个单元格移动到下一个单元格时, 一次只能移动一个单元格
 
要使用状态设计模式来建立一个九宫格移动示例, 

建立接口

IMatrix.php
php
 
   
虽然这个状态设计模式有9个状态, 分别对应九个单元格, 但一个状态最多只需要4个变迁

上下文

对于状态中的4个变迁或移动方法, 上下文必须提供相应方法来调用这些变迁方法, 另外还要完成各个状态的实例化.
Context.php
cell1 = new Cell1State($this);
        $this->cell2 = new Cell2State($this);
        $this->cell3 = new Cell3State($this);
        $this->cell4 = new Cell4State($this);
        $this->cell5 = new Cell5State($this);
        $this->cell6 = new Cell6State($this);
        $this->cell7 = new Cell7State($this);
        $this->cell8 = new Cell8State($this);
        $this->cell9 = new Cell9State($this);
        $this->currentState = $this->cell5;
    }
    //调用方法
    public function doUp()
    {
        $this->currentState->goUp();
    }
    public function doDown()
    {
        $this->currentState->goDown();
    }
    public function doLeft()
    {
        $this->currentState->goLeft();
    }
    public function doRight()
    {
        $this->currentState->goRight();
    }
    //设置当前状态
    public function setState(IMatrix $state)
    {
        $this->currentState = $state;
    }
    //获取状态
    public function getCell1State()
    {
        return $this->cell1;
    }
    public function getCell2State()
    {
        return $this->cell2;
    }
    public function getCell3State()
    {
        return $this->cell3;
    }
    public function getCell4State()
    {
        return $this->cell4;
    }
    public function getCell5State()
    {
        return $this->cell5;
    }
    public function getCell6State()
    {
        return $this->cell6;
    }
    public function getCell7State()
    {
        return $this->cell7;
    }
    public function getCell8State()
    {
        return $this->cell8;
    }
    public function getCell9State()
    {
        return $this->cell9;
    }
}

状态

9个状态表示九宫格中的不同单元格, 为了唯一显示单元格,会分别输出相应到达的单元格数字, 这样能够更清楚地看出穿过矩阵的路线.
Cell1State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '不合法的移动!
'; } public function goRight() { echo '走到2
'; $this->context->setState($this->context->getCell2State()); } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到4
'; $this->context->setState($this->context->getCell4State()); } }
Cell2State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到1
'; $this->context->setState($this->context->getCell1State()); } public function goRight() { echo '走到3
'; $this->context->setState($this->context->getCell3State()); } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到5
'; $this->context->setState($this->context->getCell5State()); } }
Cell3State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到2
'; $this->context->setState($this->context->getCell2State()); } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '不合法的移动!
'; } public function goDown() { echo '走到6
'; $this->context->setState($this->context->getCell6State()); } }
Cell4State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '不合法的移动!
'; } public function goRight() { echo '走到5
'; $this->context->setState($this->context->getCell5State()); } public function goUp() { echo '走到1
'; $this->context->setState($this->context->getCell1State()); } public function goDown() { echo '走到7
'; $this->context->setState($this->context->getCell7State()); } }
Cell5State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到4
'; $this->context->setState($this->context->getCell4State()); } public function goRight() { echo '走到6
'; $this->context->setState($this->context->getCell6State()); } public function goUp() { echo '走到2
'; $this->context->setState($this->context->getCell2State()); } public function goDown() { echo '走到8
'; $this->context->setState($this->context->getCell8State()); } }
Cell6State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到5
'; $this->context->setState($this->context->getCell5State()); } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '走到3
'; $this->context->setState($this->context->getCell3State()); } public function goDown() { echo '走到9
'; $this->context->setState($this->context->getCell9State()); } }
Cell7State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '不合法的移动!
'; } public function goRight() { echo '走到8
'; $this->context->setState($this->context->getCell8State()); } public function goUp() { echo '走到4
'; $this->context->setState($this->context->getCell4State()); } public function goDown() { echo '不合法的移动!
'; } }
Cell8State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到7
'; $this->context->setState($this->context->getCell7State()); } public function goRight() { echo '走到9
'; $this->context->setState($this->context->getCell9State()); } public function goUp() { echo '走到5
'; $this->context->setState($this->context->getCell5State()); } public function goDown() { echo '不合法的移动!
'; } }
Cell9State
context = $contextNow;
    }
    public function goLeft()
    {
        echo '走到8
'; $this->context->setState($this->context->getCell8State()); } public function goRight() { echo '不合法的移动!
'; } public function goUp() { echo '走到6
'; $this->context->setState($this->context->getCell6State()); } public function goDown() { echo '不合法的移动!
'; } }
要想有效地使用状态设计模式, 真正的难点在于要想象现实或模拟世界是怎么样
 

客户Client

下面从单元格5开始进行一个上,右,下, 下,左,上的移动
Client.php
context = new Context();
        $this->context->doUp();
        $this->context->doRight();
        $this->context->doDown();
        $this->context->doDown();
        $this->context->doLeft();
        $this->context->doUp();
    }
}
$worker = new Client();

运行结果如下

走到2
走到3
走到6
走到9
走到8
走到5

状态模式与PHP

很多人把状态设计模式看做是实现模拟器和游戏的主要方法.总的说来, 这确实是状态模式的目标,不过险些之外, 状态模型(状态引擎)和状态设计模式在PHP中也有很多应用.用PHP完成更大的项目时, 包括Facebook和WordPress, 会有更多的新增特性和当前状态需求.对于这种不断有改变和增长的情况, 就可以采用可扩展的状态模式来管理.
PHP开发人员如何创建包含多个状态的程序, 将决定状态模式的使用范围. 所以不仅状态机在游戏和模拟世界中有很多应用, 实际上状态模型还有更多适用的领域.只要PHP程序的用户会用到一组有限的状态, 开发人员就可以使用状态设计模式.
 

你可能感兴趣的:(设计模式之: 状态模式)