状态模式
当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
做同样的事情,不同意的状态下,有不同的表现行为!
状态模式介绍
定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
优点
- 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
- 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点
- 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。
应用场景
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
- 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
状态模式结构与实现
结构
- 环境类(Context)角色:
也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。 - 抽象状态(State)角色:
定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。 - 具体状态(Concrete State)角色:
实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
模板实现
- 环境类Context默认为A状态
- 每次调用状态对象的handle()方法执行不同的行为,打印不同状态下的状态信息且切换状态
运行结果用尾行注释写在Client中
/**
* 环境类
*/
class Context {
private State state;
/**
* 定义环境类的初始状态
* 初试状态设置为 ConcreteStateA
*/
public Context() {
this.state = new ConcreteStateA();
}
public void showState() {
this.state.showState();
}
/**
* 设置新状态
* @param state 状态对象
*/
public void setState(State state) {
this.state = state;
}
/**
* 读取状态
* @return 状态对象
*/
public State getState() {
return (state);
}
/**
* 对请求做处理
*/
public void handle() {
state.handle(this);
}
}
/**
* 抽象状态类
*/
abstract class State {
/**
* 将当前状态信息打印出来
*/
public abstract void showState();
/**
* 行为处理方法
* @param context 上下文
*/
public abstract void handle(Context context);
}
/**
* 具体状态A类
*/
class ConcreteStateA extends State {
@Override
public void showState() {
System.out.println("当前为A状态");
}
@Override
public void handle(Context context) {
System.out.println("AAA");
context.setState(new ConcreteStateB());
}
}
/**
* 具体状态B类
*/
class ConcreteStateB extends State {
@Override
public void showState() {
System.out.println("当前为B状态");
}
@Override
public void handle(Context context) {
System.out.println("BBB");
context.setState(new ConcreteStateA());
}
}
public class StateTemlateCilent {
public static void main(String[] args) {
Context context = new Context(); // 创建环境
context.showState();// 当前为A状态
context.handle(); // 处理请求
context.showState(); // AAA \n 当前为B状态
context.handle();
context.showState(); // BBB \n 当前为A状态
context.handle();
context.showState(); // AAA \n 当前为B状态
}
}
示例 元素战士不同元素下的攻击
在不同状态下,某方法可能什么也不做,也可能会做一些不同的行为,也有可能会自动的切换状态
- 模式
享元模式 + 状态模式 - 环境类
元素战士 - 状态类
火属性状态、水属性状态,草属性状态、无属性状态
元素战士 在不同状态下的攻击效果不一样,每次发动攻击后随机变化状态(也可能变成原状态->无变化)
package behaviour.state.attack;
/**
* 环境类
*/
class ElementWarrior {
private ElementState state;
/**
* 定义环境类的初始状态
* 初试状态设置为 无元素状态
*/
public ElementWarrior() {
this.state = new NonAttributeState();
}
/**
* 设置新状态
* @param state 状态对象
*/
public void setState(ElementState state) {
this.state = state;
}
/**
* 读取状态
* @return 状态对象
*/
public ElementState getState() {
return state;
}
/**
* 攻击
*/
public void attack() {
state.attack(this);
}
}
/**
* 抽象状态类
*/
abstract class ElementState {
/**
* 攻击
* @param warrior 战士
*/
public abstract void attack(ElementWarrior warrior);
protected void changeRandomState(ElementWarrior warrior) {
int random = (int)(Math.random() * 4);
ElementState state = null;
switch (random) {
case 0:state = new NonAttributeState();break;
case 1:state = new FireAttributeState();break;
case 2:state = new GrassAttributeState();break;
case 3:state = new WaterAttributeState();break;
default:break;
}
if (state != null && state.getClass() != this.getClass()) {
System.out.println("元素战士属性随机转换!");
warrior.setState(state);
} else {
System.out.println("元素战士保持了它的属性");
}
}
}
/**
* 具体状态:无属性状态
*/
class NonAttributeState extends ElementState {
@Override
public void attack(ElementWarrior warrior) {
System.out.println("无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]");
changeRandomState(warrior);
}
}
/**
* 具体状态:火属性状态
*/
class FireAttributeState extends ElementState {
@Override
public void attack(ElementWarrior warrior) {
System.out.println("火属性攻击:[向前方扔出一个小火球,对20米以内的单个敌人造成15点伤害,且后续三个回合,每回合结束时分别造成10、8、5伤害]");
changeRandomState(warrior);
}
}
/**
* 具体状态:火属性状态
*/
class WaterAttributeState extends ElementState {
@Override
public void attack(ElementWarrior warrior) {
System.out.println("水属性攻击:[向前方喷出高压水,对15米内直线上的所有敌人造成40~5点伤害(越远伤害越低击退效果越差),击退或击倒对方]");
changeRandomState(warrior);
}
}
/**
* 具体状态:草属性状态
*/
class GrassAttributeState extends ElementState {
@Override
public void attack(ElementWarrior warrior) {
System.out.println("草属性攻击:[丢一颗种子向10米内的单个敌人,造成5点伤害,自身回复5血量,持续3回合]");
changeRandomState(warrior);
}
}
public class Client {
public static void main(String[] args) {
final ElementWarrior w = new ElementWarrior();
w.attack();
w.attack();
w.attack();
w.attack();
w.attack();
}
}
运行结果
无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士属性随机转换!
草属性攻击:[丢一颗种子向10米内的单个敌人,造成5点伤害,自身回复5血量,持续3回合]
元素战士属性随机转换!
无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士保持了它的属性
无属性攻击:[使用拳头攻击,对1米范围以内的单个敌人造成30点伤害]
元素战士属性随机转换!
水属性攻击:[向前方喷出高压水,对15米内直线上的所有敌人造成40~5点伤害(越远伤害越低击退效果越差),击退或击倒对方]
元素战士保持了它的属性
状态模式 与 策略模式 异同
与策略模式的主要差异在 状态模式状态会自动的切换,而策略模式的策略只能主动的切换
不同点
-
状态模式重点在各状态之间的切换,从而做不同的事情;而策略模式更侧重于根据具体情况选择策略,并不涉及切换。
-
状态模式不同状态下做的事情不同,而策略模式做的都是同一件事。例如,聚合支付平台,有支付宝、微信支付、银联支付,虽然策略不同,但最终做的事情都是支付,也就是说他们之间是可替换的。反观状态模式,各个状态的同一方法做的是不同的事,不能互相替换。
-
状态模式封装了对象的状态,而策略模式封装算法或策略。因为状态是跟对象密切相关的,它不能被重用;而策略模式通过从Context中分离出策略或算法,我们可以重用它们。
-
在状态模式中,每个状态通过持有Context的引用,来实现状态转移;但是每个策略都不持有Context的引用,它们只是被Context使用。
-
状态模式将各个状态所对应的操作分离开来,即对于不同的状态,由不同的子类实现具体操作,不同状态的切换由子类实现,当发现传入参数不是自己这个状态所对应的参数,则自己给Context类切换状态;这种转换是"自动","无意识"的。状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。而策略模式是直接依赖注入到Context类的参数进行策略选择,不存在切换状态的操作。
-
策略模式的客户端必须对所有的策略类相当了解,明确当前场景下各种策略的利弊,权衡在当前场景下应该使用哪种策略,也就是是说策略类对客户端是暴露的,策略是外界给的,策略怎么变,是调用者考虑的事情,系统只是根据所给的策略做事情。
状态模式依赖于其状态的变化时其内部的行为发生变化,将动作委托到代表当前状态的对象,对外表现为类发生了变化。状态是系统自身的固有的,由系统本身控制,调用者不能直接指定或改变系统的状态转移。
相同点
状态模式和策略模式都是为具有多种可能情形设计的模式,把不同的处理情形抽象为一个相同的接口,符合对扩展开放,对修改封闭的原则。
扩展
策略模式更具有一般性一些,在实践中,可以用策略模式来封装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理,在这点上策略模式是包含状态模式的功能的,策略模式是一个重要的设计模式。