`

变化多端的状态模式(State Pattern)

阅读更多

现在写字楼越建越高,码农上个班不但要挤个地铁,还要挤个电梯。电梯的运行简单有这么几个状态:运行、停止、关闭、打开,电梯想要正常的运行,就必须得遵循一定的规则,例如运行的时候不能开门,开门状态不能运行。按照平常的逻辑,分别创建open,close,run,stop四个方法,方法里通过switch当前的状态,执行不同的动作。这种处理有几个问题:

1、扩展性太差

如果电梯还有两个状态:通电状态和断电状态。那就要在open,close,run,stop四个方法里都要增加判断条件,这与开闭原则相违背。

2、非常规状态无法实现

电梯在门开着的状态下就不能上下运行了吗?电梯有没有发生过只有运行没有停止状态呢?电梯故障嘛。还有电梯在检修的时候,可以在stop状态下不开门,这也是正常的业务需求啊。如果加上这些需求,又有多少程序要改动?

 

既然平常的办法会带来这么多问题,当然要找好的模式来解决——状态模式

实现类图如下:



在类图中,定义了一个ListState抽象类,声明了一个受保护的类型Content变量,这个是串联各个状态的封装类。封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法则了,并且还定义了四个具体的实现类,承担的是状态的产生以及状态间的转换过渡。具体实现代码:

<?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个。

 

 

 

 

 

2
1
分享到:
评论
4 楼 houxinyou 2014-05-08  
201403262452 写道
houxinyou 写道
程序结构很好,看了之后给人感觉非常清晰,但是很好地体现了开闭原则这个有点不太认同,如果增加了一种状态,必然会改变抽象类,在里面增加对应的状态方法,那么实现类里也必然要实现对应的接口方法,也就是说,增加一种状态,在增加一个状态类的,其它状态类都要改变

看的挺仔细的哈。不过如果增加一种状态,抽象类和实现类本身的方法是不需要更改的,只需要增加方法,这一点是符合开闭原则的。而并不是说整个类不作任何修改。

可能是我对开闭原则理解的不够,但我感觉应该是增加一个状态,不需要修改现有的状态类才好,但是我也没想到怎么样做才能够做到,不过现在的实现结构也很清晰了
3 楼 201403262452 2014-05-08  
houxinyou 写道
程序结构很好,看了之后给人感觉非常清晰,但是很好地体现了开闭原则这个有点不太认同,如果增加了一种状态,必然会改变抽象类,在里面增加对应的状态方法,那么实现类里也必然要实现对应的接口方法,也就是说,增加一种状态,在增加一个状态类的,其它状态类都要改变

看的挺仔细的哈。不过如果增加一种状态,抽象类和实现类本身的方法是不需要更改的,只需要增加方法,这一点是符合开闭原则的。而并不是说整个类不作任何修改。
2 楼 houxinyou 2014-05-08  
就需求来说,感觉不应该简单的给电梯定义一个状态变量,而是应该给电梯定义两个状态变量:电梯门的状态和电梯的运行状态,在电梯门的状态方法里对电梯运行状态进行判断,当电梯运行状态为运行时,电梯门状态不允许改变,反之在电梯运行状态方法里进行判断,当电梯门为打开时,电梯的运行状态不允许改变
1 楼 houxinyou 2014-05-08  
程序结构很好,看了之后给人感觉非常清晰,但是很好地体现了开闭原则这个有点不太认同,如果增加了一种状态,必然会改变抽象类,在里面增加对应的状态方法,那么实现类里也必然要实现对应的接口方法,也就是说,增加一种状态,在增加一个状态类的,其它状态类都要改变

相关推荐

    [源代码] 《易学 设计模式》 随书源代码

    第18章 变化多端:状态模式 (State) 第19章 明修栈道,暗度陈仓:策略模式 (Strategy) 第20章 循序渐进:职责链模式 (ChainofResponsibility) 第21章 独具匠心:命令模式 (Command) 第22章 步调一致:访问者模式 ...

    变化多端的云作文.doc

    变化多端的云作文.doc

    小学语文近义词变化多端的近义词

    小学语文近义词变化多端的近义词

    android 打造变化多端的SeekBar(垂直和水平)

    android 打造变化多端的SeekBar(垂直和水平)。压缩包里面有三个android项目源码。都是SeekBar相关。垂直,水平的都有。我博客地址:http://blog.csdn.net/qq_16064871。

    PRML_中译版.pdf马春鹏

    这不是⼀个简单的问题,因为⼿写体变化多端。这个问题可以使⽤⼈⼯编 写的规则解决,或者依据笔画的形状启发式地区分数字,但是实际中这样的⽅法导致了规则数 量的激增,以及不符合规则的例外等等,并且始终给出较差...

    自动测试:变化多端的输出

    在自动测试系统中,经常会遇到输出测试报告的问题。报告内容类型是一定的,大致可以分为:测试的集合的名称测试开始的时间测试结束的时间测试用例的名称测试结果测试数据测试日志...这样看上去,好像并不存在什么...

    文字图画 字符画转换器

    支持灰度/彩色/字符多种转换模式,通过自由搭配,可以生成色绚丽变化多端的文字网页;图片灰度与生成字符的对应表打破固化模式,可以自由编辑设计,满足用户设计个性化作品需要;采用友好的引导式操作,不用看帮助...

    极端风况下风力发电机组力学仿真分析

    极端风况下风力发电机组力学仿真分析,刘国良,孙文磊,风场中的风况变化多端,风力机设计时要考虑到极端风况下的风力机力学性能,在极端风况下风力机处在停机状态,通过流固耦合对风力

    简单分析js中的this的原理

    为什么this的值变化多端? 首先我们来概括下this. this是一个对象,一般存在于函数中,表示当前函数的执行上下文; 值得一提的是,当函数在执行后,this才有绑定的对象,函数未执行时,this没有内容 接下来我们看看在...

    英语四级作文句型指导大全

    英语的句型千姿百态,就像一个词可以有许多同义词一样,一种句型也可以有许多具有相同意思但不同模式的类型。我们只有对这些变化多端的句型千锤百炼,烂熟于心,才能在具体运用时应付自如,随意调遣。因此,在日常...

    写作常用句型与过渡词语

    英语的句型千姿百态,就像一个词可以有许多同义词一样,一种句型也可以有许多具有相同意思但不同模式的类型。我们只有对这些变化多端的句型千锤百炼,烂熟于心,才能在具体运用时应付自如,随意调遣。因此,在日常...

    c语言编写运动图像代码

    c语言编写的运动图像,变化多端,图像优美。 操作简单,程序不是很复杂

    C语言排序和五子棋算法.chm

    五子棋是一种受大众广泛喜爱的游戏,其规则简单,变化多端,非常富有趣味性和消遣性。这里设计和实现了一个人机对下的五子棋程序,采用了博弈树的方法,应用了剪枝和最大最小树原理进行搜索发现最好的下子位置。介绍...

    贵金属K线图解

    K线又称阴阳线或阴阳烛...从而对变化多端的市场行情有一种一目了然的直接感受。K线最大的优点是简单易懂而且运用灵活,最大的特点在于忽略了市场价格在变动过程中的各种纷繁复杂的因素。而将其基本特征展现在您的面前

    败走华容道.rar

    古老的中国游戏,以其变化多端、百玩不厌的特点与魔方、独立钻石棋一起被国外智力专家并称 为“智力游戏界的三个不可思议”。它与七巧板、九连环等中国传统益智玩具还有个代名词叫作“中国的难题”。 游戏玩法:...

    ACM图论算法学习

    图论可算是变化多端的,也许这题图论你会,另一题未必你会

Global site tag (gtag.js) - Google Analytics