现在写字楼越建越高,码农上个班不但要挤个地铁,还要挤个电梯。电梯的运行简单有这么几个状态:运行、停止、关闭、打开,电梯想要正常的运行,就必须得遵循一定的规则,例如运行的时候不能开门,开门状态不能运行。按照平常的逻辑,分别创建open,close,run,stop四个方法,方法里通过switch当前的状态,执行不同的动作。这种处理有几个问题:
1、扩展性太差
如果电梯还有两个状态:通电状态和断电状态。那就要在open,close,run,stop四个方法里都要增加判断条件,这与开闭原则相违背。
2、非常规状态无法实现
电梯在门开着的状态下就不能上下运行了吗?电梯有没有发生过只有运行没有停止状态呢?电梯故障嘛。还有电梯在检修的时候,可以在stop状态下不开门,这也是正常的业务需求啊。如果加上这些需求,又有多少程序要改动?
?
既然平常的办法会带来这么多问题,当然要找好的模式来解决——状态模式
实现类图如下:
在类图中,定义了一个ListState抽象类,声明了一个受保护的类型Content变量,这个是串联各个状态的封装类。封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法则了,并且还定义了四个具体的实现类,承担的是状态的产生以及状态间的转换过渡。具体实现代码:
class="php"><?php abstract class LiftState { protected $content; public function setContent( Content $content ) { $this->content = $content; } public abstract function open(); public abstract function close(); public abstract function run(); public abstract function stop(); } class OpenningState extends LiftState{ public function close() { $this->content->setLiftState( $this->content->closingState ); $this->content->getLiftState()->close(); } public function open() { echo "电梯门开启\n"; } public function run() {} public function stop() {} } class ClosingState extends LiftState{ public function close() { echo "电梯门关闭\n"; } public function open() { $this->content->setLiftState( $this->content->openningState ); $this->content->getLiftState()->open(); } public function run() { $this->content->setLiftState( $this->content->runningState ); $this->content->getLiftState()->run(); } public function stop() { $this->content->setLiftState( $this->content->stoppingState ); $this->content->getLiftState()->stop(); } } class RunningState extends LiftState{ public function close() {} public function open() {} public function run() { echo "电梯开动了\n"; } public function stop() { $this->content->setLiftState( $this->content->stoppingState ); $this->content->getLiftState()->stop(); } } class StoppingState extends LiftState{ public function close() {} public function open() { $this->content->setLiftState( $this->content->openningState ); $this->content->getLiftState()->open(); } public function run() { $this->content->setLiftState( $this->content->runningState ); $this->content->getLiftState()->run(); } public function stop() { echo "电梯停止了\n"; } } class Content { public $openningState; public $runningState; public $closingState; public $stoppingState; private $liftState; public function __construct() { $this->openningState = new OpenningState(); $this->runningState = new RunningState(); $this->closingState = new ClosingState(); $this->stoppingState = new StoppingState(); } public function getLiftState() { return $this->liftState; } public function setLiftState( LiftState $liftState ) { $this->liftState = $liftState; $this->liftState->setContent( $this ); } public function open() { $this->liftState->open(); } public function close() { $this->liftState->close(); } public function run() { $this->liftState->run(); } public function stop() { $this->liftState->stop(); } } $content = new Content(); $content->setLiftState( new ClosingState() ); $content->open(); $content->close(); $content->run(); $content->stop(); $content->close(); ?> 运行结果: 电梯门开启 电梯门关闭 电梯开动了 电梯停止了 [Finished in 0.1s]
?
?
状态模式的定义
当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。看看状态模式的三个角色:
1、State——抽象状态角色
接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换
2、ConcreteState——具体状态角色
每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态
3、Content——环境角色
定义客户端需要的接口,并且负责具体状态的切换
?
状态模式相对比较复杂,它提供了一种对物质运动的另一个观察视角,通过状态变更促使行为的变化,就类似水的状态变更一样,一碗水的初始状态是液态,通过加热转变为气态,状态的改变同时也引起体积的扩大,然后就产生了一个新的行为。
?
?
状态模式的优点
1、结构清晰
避免了过多的swith...case或if...else语句的使用,避免了程序的复杂性,提高系统的可维护性
2、遵循设计原则
很好地体现了开闭原则和单一职责原则,每一个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了。
3、封装性非常好
这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
?
?
状态模式的缺点
子类会太多,也就是类膨胀,不好管理。
?
?
状态模式的使用场景
1、行为随状态改变而改变的场景
这也是状态模式的根本出发点,例如权限设计,人员的状态不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式
2、条件、分支判断语句的替代者
在程序中大量使用switch语句或者if判断语句会导致程序结构不清晰,逻辑混乱,使用状态模式可以很好地避免这一问题,它通过扩展子类实现了条件的判断处理
?
?
状态模式的注意事项
状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。
?
?
?
?
?