状态模式定义:当一个对象内在状态改变时运行其改变行为,这个对象看起来像改变了其类。
在我们if else超过3层的时候,很多时候我们会想到状态模式,其可以使代码结构看起来更清晰也利于扩展,其核心就是封装性,将状态的改变封装起来,客户端不用关心状态的改变,但是实际内部是有状态的转换的。
首先依旧是假定一个场景:我们平常玩网游的时候都会有杀人系统,这里简化一下,玩家分为白名,黄名,红名玩家。白名玩家是不可以杀人和被杀的,但是可以手动开启PK,则会进入黄名状态;黄名状态可以杀别的玩家,杀人后变成红名状态,但是杀人前还是黄名状态时不可以被别的玩家杀害;红名玩家可以杀人和被杀,被杀后状态变为黄名。这就是一个很典型的状态模式的例子,如果不用状态模式,我们在杀人、被杀、状态转换这些方法中都要加if else来判断玩家当前状态进行不同的处理,代码会很难看且臃肿,用状态模式则会清晰很多,下面来看类图:
从图中可以看到,本例分为三种状态WhitePlayer、YellowPlayer、RedPlayer,其只需要管自己状态时内部三个方法的实现即可,下面来看具体实现:
状态接口IState
public interface IState {
/**
* 击杀其他玩家
*/
void killPlayer();
/**
* 被其他玩家击杀
*/
void beKilled();
/**
* 更改状态
*/
void transferState();
}
玩家状态抽象类PlayerState
public abstract class PlayerState implements IState {
protected Context context;
public void setContext(Context context) {
this.context = context;
}
@Override
public abstract void killPlayer();
@Override
public abstract void beKilled();
@Override
public abstract void transferState();
}
白名玩家实现类WhitePlayer
public class WhitePlayer extends PlayerState {
@Override
public void killPlayer() {
System.out.println("白名玩家不能击杀其他玩家");
}
@Override
public void beKilled() {
System.out.println("白名玩家不可以被其他玩家击杀");
}
/**
* 白名玩家可以自己手动开启PK模式,状态变为黄名玩家
*/
@Override
public void transferState() {
super.context.setPlayerState(new YellowPlayer());
System.out.println("我开启了PK模式,变成了黄名玩家!");
}
}
黄名玩家实现类YellowPlayer
public class YellowPlayer extends PlayerState {
/**
* 黄名玩家可以击杀其他玩家,击杀后玩家变成红名玩家
*/
@Override
public void killPlayer() {
System.out.println("我杀人啦!!!!!!!");
super.context.setPlayerState(new RedPlayer());
}
@Override
public void beKilled() {
System.out.println("黄名玩家不可以被其他玩家击杀");
}
/**
* 黄名玩家可以自己关闭PK模式,状态变为白名玩家
*/
@Override
public void transferState() {
super.context.setPlayerState(new WhitePlayer());
System.out.println("我关闭了PK模式,变成了白名玩家!");
}
}
红名玩家实现类RedPlayer
public class RedPlayer extends PlayerState {
/**
* 红名玩家可以击杀其他玩家
*/
@Override
public void killPlayer() {
System.out.println("我杀了很多人啦!!!!");
}
/**
* 红名玩家可以被其他玩家击杀,击杀后变为黄名
*/
@Override
public void beKilled() {
System.out.println("我被杀了!!!so sad!!!");
super.context.setPlayerState(new YellowPlayer());
}
/**
* 红名玩家不能主动转换状态
*/
@Override
public void transferState() {
System.out.println("我不能转换自己的状态了!");
}
}
场景控制类Context
public class Context implements IState {
private PlayerState playerState;
public void setPlayerState(PlayerState playerState) {
this.playerState = playerState;
playerState.setContext(this);
}
@Override
public void killPlayer() {
playerState.killPlayer();
}
@Override
public void beKilled() {
playerState.beKilled();
}
@Override
public void transferState() {
playerState.transferState();
}
}
下面来看客户端代码,很简单
public static void main(String[] args) {
Context context = new Context();
// 玩家初始都是白名玩家
context.setPlayerState(new WhitePlayer());
// 白名玩家尝试杀人
context.killPlayer();
// 不行,我要开启PK杀人
context.transferState();
// 刚开了PK还没杀人呢,就有人想杀我
context.beKilled();
// 我去杀人了
context.killPlayer();
// 有人来杀我了
context.beKilled();
// 被杀了,还是白名安全
context.transferState();
// oh yeah ,不能被杀了
context.beKilled();
}
运行结果如下:
白名玩家不能击杀其他玩家
我开启了PK模式,变成了黄名玩家!
黄名玩家不可以被其他玩家击杀
我杀人啦!!!!!!!
我被杀了!!!so sad!!!
我关闭了PK模式,变成了白名玩家!
白名玩家不可以被其他玩家击杀
可以看到,客户端是不关心内部状态的改变的,只需要按照我们平常的操作流程进行操作而已,通篇没有一个if else,代码看起来很清晰。
总结:
优点:
- 结构清晰,避免出现过多臃肿的if else。
- 封装性强,客户端无需关心状态的转换。
- 易于扩展,显然新增一种状态要比每个方法中都加一层else if要容易的多。
缺点:每个状态对应一个类,状态越多类越多,可能会造成类过多。
欢迎关注个人博客:blog.scarlettbai.com