设计模式解密(14)- 状态模式
1、简介
定义:允许对象在内部状态改变时改变它的行为, 对象看起来好像修改了它的类。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
本质:根据状态来分离和选择行为。
英文:state
类型:行为型
2、问题引入
大家都在玩王者农药,这里就以王者农药的英雄为例,讲解状态模式,问题:模拟英雄移动状态的变化!!!
这里简单模拟,实现四个状态:正常状态、加速状态、减速状态、眩晕状态;
3、普通方式实现
package com.designpattern.state.common_realize; /** * 模拟英雄类状态变化 * @author Json<<json1990@foxmail.com>> */ public class Hero { /** * 正常状态 */ public static final int NORMAL_STATE = 1; /** * 加速状态 */ public static final int EXPEDITE_STATE = 2; /** * 减速状态 */ public static final int DECELERATE_STATE = 3; /** * 眩晕状态 */ public static final int DIZZINESS_STATE = 4; //保存英雄当前状态,默认是正常状态 private int state = NORMAL_STATE; //英雄移动线程 private Thread moveThread; //设置状态 public void setState(int state) { this.state = state; } //开始移动 public void startMove() { if (isMoving()) { return; } final Hero hero = this; moveThread = new Thread(new Runnable() { public void run() { while (!moveThread.isInterrupted()) { try { hero.move(); } catch (InterruptedException e) { break; } } } }); System.out.println("英雄开始移动."); moveThread.start(); } //停止移动 public void stopMove() { if (isMoving()) moveThread.interrupt(); System.out.println("英雄停止移动."); } //判断英雄是否在移动 private boolean isMoving(){ return moveThread != null && !moveThread.isInterrupted(); } //英雄类处理移动 private void move() throws InterruptedException{ if (state == EXPEDITE_STATE) { System.out.println("加速状态,英雄加速移动"); //假设加速持续5秒 Thread.sleep(5000); state = NORMAL_STATE; System.out.println("加速状态结束,恢复正常状态"); }else if (state == DECELERATE_STATE) { System.out.println("减速状态,英雄減速移动"); //假设减速持续2.5秒 Thread.sleep(2500); state = NORMAL_STATE; System.out.println("减速状态结束,恢复正常状态"); }else if (state == DIZZINESS_STATE) { System.out.println("眩晕状态,英雄停止移动"); //假设眩晕持续1.5秒 Thread.sleep(1500); state = NORMAL_STATE; System.out.println("眩晕状态结束,恢复正常状态"); }else { //正常跑动则不打印内容,会刷屏 } } }
测试:
package com.designpattern.state.common_realize; /** * 测试 * @author Json<<json1990@foxmail.com>> */ public class Client { public static void main(String[] args) throws InterruptedException { Hero hero = new Hero(); hero.startMove(); System.out.println("队友给了一个加速技能↓↓↓"); hero.setState(Hero.EXPEDITE_STATE); Thread.sleep(10000);//在过10秒,被对方英雄虚弱 System.out.println("被对方英雄虚弱了↓↓↓"); hero.setState(Hero.DECELERATE_STATE); Thread.sleep(10000);//在过10秒,被对方英雄眩晕 System.out.println("被对方英雄眩晕了↓↓↓"); hero.setState(Hero.DIZZINESS_STATE); Thread.sleep(10000);//在过10秒,游戏结束 hero.stopMove(); } }
结果:
英雄开始移动.
队友给了一个加速技能↓↓↓
加速状态,英雄加速移动
加速状态结束,恢复正常状态
被对方英雄虚弱了↓↓↓
减速状态,英雄減速移动
减速状态结束,恢复正常状态
被对方英雄眩晕了↓↓↓
眩晕状态,英雄停止移动
眩晕状态结束,恢复正常状态
英雄停止移动.
看起来很简单?你要明白这里只是实现了方法示意(没有加具体的逻辑,处理方法可能会很复杂,可能需要处理很多东西),如果把每个功能都完整的实现出来,那move()方法会很长的,你想想,在move()方法中那么多判断,还有每个判断对应的功能处理都放在一起,成篇的代码,找个bug能看很长时间,都不一定能屡明白,简直就是痛苦。
如果我们有新的状态加入的话,势必要在业务方法里边增加相应的else if 语句。如果每种情况里的逻辑非常复杂,代码很长, 那你再改move()方法,会很揪心,看半天,才能下手去改;就没有把move()方法简化的办法?
当然有,下面咱们引入状态模式!
4、借机引入状态模式
(引)类图:
组成:
Context(上下文):通常用来定义客户感兴趣的接口,同时维护一个来具体处理当前状态的实例对象。
State(状态接口):用来封装与上下文的一个特定状态所对应的行为。
ConcreteState(具体实现状态处理的类):每个类实现一个跟上下文相关的状态的具体处理。
代码结构:
/** * 封装与Context的一个特定状态相关的行为 */ public interface State { /** * 状态对应的处理 * @param sampleParameter 示例参数,说明可以传入参数,具体传入 * 什么样的参数,传入几个参数,由具体应用来具体分析 */ public void handle(String sampleParameter); } /** * 实现一个与Context的一个特定状态相关的行为 */ public class ConcreteStateA implements State { public void handle(String sampleParameter) { //实现具体的处理 } } /** * 实现一个与Context的一个特定状态相关的行为 */ public class ConcreteStateB implements State { public void handle(String sampleParameter) { //实现具体的处理 } } /** * 定义客户感兴趣的接口,通常会维护一个State类型的对象实例 */ public class Context { /** * 持有一个State类型的对象实例 */ private State state; /** * 设置实现State的对象的实例 * @param state 实现State的对象的实例 */ public void setState(State state) { this.state = state; } /** * 用户感兴趣的接口方法 * @param sampleParameter 示意参数 */ public void request(String sampleParameter) { //在处理中,会转调state来处理 state.handle(sampleParameter); } }
5、状态模式重写实例
package com.designpattern.state.pattern_realize; /** * 封装与Context的一个特定状态相关的行为:英雄移动 * @author Json<<json1990@foxmail.com>> */ public interface State { /** * 处理移动状态 * @param hero */ public void move(Hero hero); }
package com.designpattern.state.pattern_realize; /** * 特定状态 -- 正常状态 * @author Json<<json1990@foxmail.com>> */ public class NormalState implements State{ @Override public void move(Hero hero) { //正常移动则不打印内容,否则会刷屏 } }
package com.designpattern.state.pattern_realize; /** * 特定状态 -- 加速状态 * @author Json<<json1990@foxmail.com>> */ public class ExpediteState implements State { @Override public void move(Hero hero) { System.out.println("加速状态,英雄加速移动"); //假设加速持续5秒 try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } hero.setState(hero.NORMAL_STATE); System.out.println("加速状态结束,恢复正常状态"); } }
package com.designpattern.state.pattern_realize; /** * 特定状态 -- 减速状态 * @author Json<<json1990@foxmail.com>> */ public class DecelerateState implements State { @Override public void move(Hero hero) { System.out.println("减速状态,英雄減速移动"); //假设减速持续2.5秒 try { Thread.sleep(2500); } catch (InterruptedException e) { e.printStackTrace(); } hero.setState(hero.NORMAL_STATE); System.out.println("减速状态结束,恢复正常状态"); } }
package com.designpattern.state.pattern_realize; /** * 特定状态 -- 眩晕状态 * @author Json<<json1990@foxmail.com>> */ public class DizzinessState implements State { @Override public void move(Hero hero) { System.out.println("眩晕状态,英雄停止移动"); //假设眩晕持续1.5秒 try { Thread.sleep(1500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } hero.setState(hero.NORMAL_STATE); System.out.println("眩晕状态结束,恢复正常状态"); } }
package com.designpattern.state.pattern_realize; /** * 上下文:模拟英雄类 * @author Json<<json1990@foxmail.com>> */ public class Hero { /** * 正常状态 */ public static final State NORMAL_STATE = new NormalState(); /** * 加速状态 */ public static final State EXPEDITE_STATE = new ExpediteState(); /** * 减速状态 */ public static final State DECELERATE_STATE = new DecelerateState(); /** * 眩晕状态 */ public static final State DIZZINESS_STATE = new DizzinessState(); //保存英雄当前状态,默认是正常状态 private State state = NORMAL_STATE; //英雄移动线程 private Thread moveThread; //设置状态 public void setState(State state) { this.state = state; } //开始移动 public void startMove() { if (isMoving()) { return; } final Hero hero = this; moveThread = new Thread(new Runnable() { public void run() { while (!moveThread.isInterrupted()) { state.move(hero); } } }); System.out.println("英雄开始移动."); moveThread.start(); } //停止移动 public void stopMove() { if (isMoving()) moveThread.interrupt(); System.out.println("英雄停止移动."); } //判断英雄是否在移动 private boolean isMoving(){ return moveThread != null && !moveThread.isInterrupted(); } }
测试:
package com.designpattern.state.pattern_realize; import com.designpattern.state.common_realize.Hero; /** * 测试 * @author Json<<json1990@foxmail.com>> */ public class Client { public static void main(String[] args) throws InterruptedException { Hero hero = new Hero(); hero.startMove(); System.out.println("队友给了一个加速技能↓↓↓"); hero.setState(Hero.EXPEDITE_STATE); Thread.sleep(10000);//在过10秒,被对方英雄虚弱 System.out.println("被对方英雄虚弱了↓↓↓"); hero.setState(Hero.DECELERATE_STATE); Thread.sleep(10000);//在过10秒,被对方英雄眩晕 System.out.println("被对方英雄眩晕了↓↓↓"); hero.setState(Hero.DIZZINESS_STATE); Thread.sleep(10000);//在过10秒,游戏结束 hero.stopMove(); } }
结果:
英雄开始移动.
队友给了一个加速技能↓↓↓
加速状态,英雄加速移动
加速状态结束,恢复正常状态
被对方英雄虚弱了↓↓↓
减速状态,英雄減速移动
减速状态结束,恢复正常状态
被对方英雄眩晕了↓↓↓
眩晕状态,英雄停止移动
眩晕状态结束,恢复正常状态
英雄停止移动.
下面来分析一下改造后代码:
不用说,状态模式是可以解决我们上面的if else结构的,我们采用状态模式,利用多态的特性可以消除掉if else结构。这样所带来的好处就是可以大大的增加程序的可维护性与扩展性。
联想出来的优点:
1、我们去掉了if else结构,使得代码的可维护性更强,不易出错,这个优点挺明显,如果更改移动的方法,明显比在一堆 if else 里,修改要好;
2、使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,会非常的容易。
3、状态是可以被共享的,这个在上面的例子当中有体现,看下Hero类当中的四个static final变量就知道了,因为状态类一般是没有自己的内部状态的,所有它只是一个具有行为的对象,因此是可以被共享的。
4、状态的转换更加简单安全,简单体现在状态的分割,因为我们把一堆if else分割成了若干个代码段分别放在几个具体的状态类当中,所以转换起来当然更简单,而且每次转换的时候我们只需要关注一个固定的状态到其他状态的转换。安全体现在类型安全,我们设置上下文的状态时,必须是状态接口的实现类,而不是原本的一个整数,这可以杜绝魔数(别人可能读不懂你的状态码什么意思,如果有你在没写注释,还要去查看源码才能明白)以及不正确的状态码。
6、优缺点
优点:
结构清晰:避免了过多的switch...case或者if...else语句的使用,避免了程序的复杂性,提供系统的可维护性。
遵循设计原则:很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。
封装性非常好:这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。
缺点 :
如果完全使用状态模式就会有太多的子类,也就是类膨胀,不好管理。
7、应用场景
1、行为随状态改变而改变的场景。可以使用状态模式,来把状态和行为分离开,虽然分离开了,但状态和行为是有对应关系的,可以在运行期间,通过改变状态,就能够调用到该状态对应的状态处理对象上去,从而改变对象的行为。
2、代码含有庞大的switch语句或者if判断语句,而且这些分支依赖于该对象的状态。可以使用状态模式,把各个分支的处理分散包装到单独的对象处理类里面,这样,这些分支对应的对象就可以不依赖于其它对象而独立变化了。
8、策略模式和状态模式
这两个模式从模式结构上看是一样的,但是实现的功能是不一样的。
状态模式是根据状态的变化来选择相应的行为,不同的状态对应不同的类,每个状态对应的类实现了该状态对应的功能,在实现功能的同时,还会维护状态数据的变化。这些实现状态对应的功能的类之间是不能相互替换的。
策略模式是根据需要或者是客户端的要求来选择相应的实现类,各个实现类是平等的,是可以相互替换的。
另外策略模式可以让客户端来选择需要使用的策略算法,而状态模式一般是由上下文,或者是在状态实现类里面来维护具体的状态数据,通常不由客户端来指定状态。
9、总结
需要注意的地方:状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式;
PS:源码地址 https://github.com/JsonShare/DesignPattern/tree/master
PS:原文地址 http://www.cnblogs.com/JsonShare/p/7246915.html