(二十一)状态模式详解(DOTA版)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可。
本次LZ给各位介绍状态模式,之前在写设计模式的时候,引入了一些小故事,二十章职责连模式是故事版的最后一篇,之后还剩余四个设计模式,LZ会依照原生的方式去解释这几个设计模式,特别是原型模式和解释器模式,会包含一些其它的内容。
好了,接下来,我们先来看看状态模式的定义吧。
定义:(源于Design Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
上述是百度百科中对状态模式的定义,定义很简单,只有一句话,请各位形象的去理解这句话,它说当状态改变时,这个对象的行为也会变,而看起来就像是这个类改变了一样。
这正是应验了我们那句话,有些人一旦发生过什么事以后,就像变了个人似的,这句话其实与状态模式有异曲同工之妙。
我们仔细体会一下定义当中的要点。
1、有一个对象,它是有状态的。
2、这个对象在状态不同的时候,行为不一样。
3、这些状态是可以切换的,而非毫无关系。
前两点比较好理解,第3点有时候容易给人比较迷惑的感觉,什么叫这些状态是可以切换的,而非毫无关系?
举个例子,比如一个人的状态,可以有很多,像生病和健康,这是两个状态,这是有关系并且可以转换的两个状态。再比如,睡觉、上班、休息,这也算是一组状态,这三个状态也是有关系的并且可以互相转换。
那如果把生病和休息这两个状态放在一起,就显得毫无意义了。所以这些状态应该是一组相关并且可互相切换的状态。
下面我们来看看状态模式的类图。
类图中包含三个角色。
Context:它就是那个含有状态的对象,它可以处理一些请求,这些请求最终产生的响应会与状态相关。
State:状态接口,它定义了每一个状态的行为集合,这些行为会在Context中得以使用。
ConcreteState:具体状态,实现相关行为的具体状态类。
如果针对刚才对于人的状态的例子来分析,那么人(Person)就是Context,状态接口依然是状态接口,而具体的状态类,则可以是睡觉,上班,休息,这一系列状态。
不过LZ也看过不少状态模式的文章和帖子,包括《大话设计模式》当中,都举的是有关人的状态的例子,所以这里给大家换个口味,我们换一个例子。
我们来试着写一个DOTA的例子,最近貌似跟DOTA干上了,不为其他,就因为DOTA伴随了LZ四年的大学时光。
玩过的朋友都知道,DOTA里的英雄有很多状态,比如正常,眩晕,加速,减速等等。相信就算没有玩过DOTA的朋友们,在其它游戏里也能见到类似的情况。那么假设我们的DOTA没有使用状态模式,则我们的英雄类会非常复杂和难以维护,我们来看下,原始版的英雄类是怎样的。
package com.state; //英雄类
public class Hero { public static final int COMMON = 1;//正常状态 public static final int SPEED_UP = 2;//加速状态 public static final int SPEED_DOWN = 3;//减速状态 public static final int SWIM = 4;//眩晕状态 private int state = COMMON;//默认是正常状态 private Thread runThread;//跑动线程 //设置状态
public void setState(int state) {
this.state = state;
}
//停止跑动
public void stopRun() {
if (isRunning()) runThread.interrupt();
System.out.println("--------------停止跑动---------------");
}
//开始跑动
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
public void run() {
while (!runThread.isInterrupted()) {
try {
hero.run();
} catch (InterruptedException e) {
break;
}
}
}
});
System.out.println("--------------开始跑动---------------");
runThread.start();
} private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
//英雄类开始奔跑
private void run() throws InterruptedException{
if (state == SPEED_UP) {
System.out.println("--------------加速跑动---------------");
Thread.sleep(4000);//假设加速持续4秒
state = COMMON;
System.out.println("------加速状态结束,变为正常状态------");
}else if (state == SPEED_DOWN) {
System.out.println("--------------减速跑动---------------");
Thread.sleep(4000);//假设减速持续4秒
state = COMMON;
System.out.println("------减速状态结束,变为正常状态------");
}else if (state == SWIM) {
System.out.println("--------------不能跑动---------------");
Thread.sleep(2000);//假设眩晕持续2秒
state = COMMON;
System.out.println("------眩晕状态结束,变为正常状态------");
}else {
//正常跑动则不打印内容,否则会刷屏
}
} }
下面我们写一个客户端类,去试图让英雄在各种状态下奔跑一下。
package com.state; public class Main { public static void main(String[] args) throws InterruptedException {
Hero hero = new Hero();
hero.startRun();
hero.setState(Hero.SPEED_UP);
Thread.sleep(5000);
hero.setState(Hero.SPEED_DOWN);
Thread.sleep(5000);
hero.setState(Hero.SWIM);
Thread.sleep(5000);
hero.stopRun();
}
}
可以看到,我们的英雄在跑动过程中随着状态的改变,会以不同的状态进行跑动。
在上面原始的例子当中,我们的英雄类当中有明显的if else结构,我们再来看看百度百科中状态模式所解决的问题的描述。
状态模式解决的问题:状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
不用说,状态模式是可以解决我们上面的if else结构的,我们采用状态模式,利用多态的特性可以消除掉if else结构。这样所带来的好处就是可以大大的增加程序的可维护性与扩展性。
下面我们就使用状态模式对上面的例子进行改善,首先第一步,就是我们需要定义一个状态接口,这个接口就只有一个方法,就是run。
package com.state; public interface RunState { void run(Hero hero); }
与状态模式类图不同的是,我们加入了一个参数Hero(Context),这样做的目的是为了具体的状态类当达到某一个条件的时候可以切换上下文的状态。下面列出四个具体的状态类,其实就是把if else拆掉放到这几个类的run方法中。
package com.state; public class CommonState implements RunState{ public void run(Hero hero) {
//正常跑动则不打印内容,否则会刷屏
} }
package com.state; public class SpeedUpState implements RunState{ public void run(Hero hero) {
System.out.println("--------------加速跑动---------------");
try {
Thread.sleep(4000);//假设加速持续4秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------加速状态结束,变为正常状态------");
} }
package com.state; public class SpeedDownState implements RunState{ public void run(Hero hero) {
System.out.println("--------------减速跑动---------------");
try {
Thread.sleep(4000);//假设减速持续4秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------减速状态结束,变为正常状态------");
} }
package com.state; public class SwimState implements RunState{ public void run(Hero hero) {
System.out.println("--------------不能跑动---------------");
try {
Thread.sleep(2000);//假设眩晕持续2秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------眩晕状态结束,变为正常状态------");
} }
这下我们的英雄类也要相应的改动一下,最主要的改动就是那些if else可以删掉了,如下。
package com.state; //英雄类
public class Hero { public static final RunState COMMON = new CommonState();//正常状态 public static final RunState SPEED_UP = new SpeedUpState();//加速状态 public static final RunState SPEED_DOWN = new SpeedDownState();//减速状态 public static final RunState SWIM = new SwimState();//眩晕状态 private RunState state = COMMON;//默认是正常状态 private Thread runThread;//跑动线程 //设置状态
public void setState(RunState state) {
this.state = state;
}
//停止跑动
public void stopRun() {
if (isRunning()) runThread.interrupt();
System.out.println("--------------停止跑动---------------");
}
//开始跑动
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
public void run() {
while (!runThread.isInterrupted()) {
state.run(hero);
}
}
});
System.out.println("--------------开始跑动---------------");
runThread.start();
} private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
} }
可以看到,现在我们的英雄类优雅了许多,我们使用刚才同样的客户端运行即可得到同样的结果。
对比我们的原始例子,现在我们使用状态模式之后,有几个明显的优点:
一、我们去掉了if else结构,使得代码的可维护性更强,不易出错,这个优点挺明显,如果试图让你更改跑动的方法,是刚才的一堆if else好改,还是分成了若干个具体的状态类好改呢?答案是显而易见的。
二、使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,假设有加速20%,加速10%,减速10%等等等(这并不是虚构,DOTA当中是真实存在这些状态的),会非常的容易。
三、状态是可以被共享的,这个在上面的例子当中有体现,看下Hero类当中的四个static final变量就知道了,因为状态类一般是没有自己的内部状态的,所有它只是一个具有行为的对象,因此是可以被共享的。
四、状态的转换更加简单安全,简单体现在状态的分割,因为我们把一堆if else分割成了若干个代码段分别放在几个具体的状态类当中,所以转换起来当然更简单,而且每次转换的时候我们只需要关注一个固定的状态到其他状态的转换。安全体现在类型安全,我们设置上下文的状态时,必须是状态接口的实现类,而不是原本的一个整数,这可以杜绝魔数以及不正确的状态码。
状态模式适用于某一个对象的行为取决于该对象的状态,并且该对象的状态会在运行时转换,又或者有很多的if else判断,而这些判断只是因为状态不同而不断的切换行为。
上面的适用场景是很多状态模式的介绍中都提到的,下面我们就来看下刚才DOTA中,英雄例子的类图。
可以看到,这个类图与状态模式的标准类图是几乎一模一样的,只是多了一条状态接口到上下文的依赖线,而这个是根据实际需要添加的,而且一般情况下都是需要的。
状态模式也有它的缺点,不过它的缺点和大多数模式相似,有两点。
1、会增加的类的数量。
2、使系统的复杂性增加。
尽管状态模式有着这样的缺点,但是往往我们牺牲复杂性去换取的高可维护性和扩展性是相当值得的,除非增加了复杂性以后,对于后者的提升会乎其微。
状态模式在项目当中也算是较经常会碰到的一个设计模式,但是通常情况下,我们还是在看到if else的情况下,对项目进行重构时使用,又或者你十分确定要做的项目会朝着状态模式发展,一般情况下,还是不建议在项目的初期使用。
好了,本次状态模式的分享就到此结束了,希望各位有所收获。
(二十一)状态模式详解(DOTA版)的更多相关文章
- Java设计模式之状态模式详解
(本文由言念小文原创,转载请注明出处) 在实际工作中经常遇到某个对象,处于不同的状态有不同行为逻辑.且状态之间可以相互迁移的业务场景,特别是在开发通信协议栈类软件中尤为多见.<设计模式之禅> ...
- Linux学习之CentOS(二十一)--Linux系统启动详解
在这篇随笔里面将对Linux系统的启动进行一个详细的解释!我的实验机器是CentOS6.4,当然对于现有的Linux发行版本,其系统的启动基本上都是一样的! 首先我们来看下Linux系统启动的几个 ...
- 二:flask-debug模式详解
debug模式的情况下可以抛出详细异常信息 新建一个脚本并运行 访问 此时是非debug模式,如果运行的时候代码报错了,是不会提示详细错误的,只会报服务器内部错误 开启debug模式,可以查看到详细错 ...
- (原创)LAMP搭建之二:apache配置文件详解(中英文对照版)
LAMP搭建之二:apache配置文件详解(中英文对照版) # This is the main Apache server configuration file. It contains the # ...
- Solr系列二:solr-部署详解(solr两种部署模式介绍、独立服务器模式详解、SolrCloud分布式集群模式详解)
一.solr两种部署模式介绍 Standalone Server 独立服务器模式:适用于数据规模不大的场景 SolrCloud 分布式集群模式:适用于数据规模大,高可靠.高可用.高并发的场景 二.独 ...
- Java8初体验(二)Stream语法详解---符合人的思维模式,数据源--》stream-->干什么事(具体怎么做,就交给Stream)--》聚合
Function.identity()是什么? // 将Stream转换成容器或Map Stream<String> stream = Stream.of("I", & ...
- JavaScript严格模式详解
转载自阮一峰的博客 Javascript 严格模式详解 作者: 阮一峰 一.概述 除了正常运行模式,ECMAscript 5添加了第二种运行模式:"严格模式"(strict m ...
- Zookeeper系列二:分布式架构详解、分布式技术详解、分布式事务
一.分布式架构详解 1.分布式发展历程 1.1 单点集中式 特点:App.DB.FileServer都部署在一台机器上.并且访问请求量较少 1.2 应用服务和数据服务拆分 特点:App.DB.Fi ...
- Velocity魔法堂系列二:VTL语法详解
一.前言 Velocity作为历史悠久的模板引擎不单单可以替代JSP作为Java Web的服务端网页模板引擎,而且可以作为普通文本的模板引擎来增强服务端程序文本处理能力.而且Velocity被移植到不 ...
随机推荐
- 烂泥:centos单独编译安装gd库
本文由秀依林枫提供友情赞助,首发于烂泥行天下. 这几天一直在弄一个商城系统,该系统的源码及数据库都已经上传并创建完毕.但是在安装该系统时,却提示缺少gd库.如下: 使用php探针查看,发现php确实没 ...
- 烂泥:mysql数据库使用的基本命令
本文由秀依林枫提供友情赞助,首发于烂泥行天下. 1.连接数据库的格式 mysql -h IP -u用户名 -p密码; 1.1连接远程数据库 mysql -h 192.168.1.214 -uroot ...
- linux下使用Apache+php实现留言板功能的网站
一.首先我们的linux服务器上要安装Apache和php 请参考:http://www.cnblogs.com/dagege/p/5949620.html 二.关闭防火墙服务,关闭selinux 请 ...
- Can't load AMD 64-bit .dll on a IA 32-bit platform
主要谈谈在win8.1(64bit)下搭建环境的经历. 安装win8.1(64bit)后,配置java环境是费了我一番心思的,所以想记录下来,成为经验.64位系统下比较理想的配置应该是 64位jdk ...
- 【转】Android编程判断手机or平板
转载自:http://www.cnblogs.com/sunzn/p/3663363.html /** * 判断当前设备是手机还是平板,代码来自 Google I/O App for Android ...
- elasticsearch 集群搭建
需要编辑的文件是config/elasticsearch.yml文件 需要配置的项目有: # Use a descriptive name for your cluster: # cluster.na ...
- mapred-site.xml 配置在线更新
环境:ibm jdk , cdh2.35.0.2 需求:更新mapred-site.xml 中的mapreduce.map.java.opts 和 mapreduce.reduce.java.opts ...
- WPF之全局快捷键
目录 1.WPF快捷键实现方式 2.全局快捷键设置界面 3.Windows API调用 4.注册全局快捷键 5.快捷键触发 WPF快捷键实现方式 WPF快捷键实现主要有自定义快捷键命令和全局快捷键两种 ...
- IE6下png格式图片显示问题
一开始是使用 _filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='images/bgBtn.png'); /* IE6 * ...
- 我懒蛋又回来了!-PDO
hi 好几天了吧,脚伤都有一周了的.玩乐的这么久才发觉,对于年轻人,或者更具体的,对我而言,受伤最难受的不是受伤瞬间的身痛,不是随之而来的心理负担,不是独自一人远在他乡的孤独无助之感:最伤的是斗志,是 ...