一、场景
同一个东西(例如:媒体播放器),有一些操作:暂停、播放、停止。很显然,每执行一种操作,媒体播放器的状态便改变了。
播放 -> 暂停:
- Media Player is now paused.
当前是播放状态,点击暂停操作,输出:Media Player is not playing.
暂停 -> 暂停
- Media Player is not playing.
当前是暂停状态,点击暂停操作,输出:Media Player is not playing.
虽然是同一种操作,但是,因为媒体播放器处在不同的状态,所以表现不同。
面对这种场景,我们怎么编码呢?
简单粗暴的方式:写if-else或者switch。(详见:二、不采用状态模式)
更好的方式:采用状态模式。(详见:三、采用状态模式)
- 状态模式是一种行为设计模式, 让我们能在一个对象的内部状态变化时改变其行为。
二、不采用状态模式
1、代码
// 媒体播放器
public class MediaPlayer {
private String currentState;
public MediaPlayer() {
this.currentState = "STOPPED"; // 初始状态为停止
}
public void play() {
if (currentState.equals("STOPPED")) {
System.out.println("Media Player is now playing.");
currentState = "PLAYING";
} else if (currentState.equals("PAUSED")) {
System.out.println("Media Player resumed playing.");
currentState = "PLAYING";
} else {
System.out.println("Media Player is already playing.");
}
}
public void pause() {
if (currentState.equals("PLAYING")) {
System.out.println("Media Player is now paused.");
currentState = "PAUSED";
} else {
System.out.println("Media Player is not playing.");
}
}
public void stop() {
System.out.println("Media Player is now stopped.");
currentState = "STOPPED";
}
}
// 客户端
public class Main {
public static void main(String[] args) {
MediaPlayer mediaPlayer = new MediaPlayer();
System.out.println("Initial state: Stopped");
mediaPlayer.play(); // Stopped -> Playing
mediaPlayer.pause(); // Playing -> Paused
mediaPlayer.play(); // Paused -> Playing
mediaPlayer.stop(); // Playing -> Stopped
}
}
/*
Initial state: Stopped
Media Player is now playing.
Media Player is now paused.
Media Player resumed playing.
Media Player is now stopped.
*/
2、缺点
- 由于在设计之初,不一定能想到所有状态,假设一开始只想到了:play、pause、stop。辛辛苦苦写好了上面的代码。这时候,产品经理又提了新需求了,发现又要补2个状态。没办法,上面的play、pause、stop三个方法都要进行修改。改着改着就成屎山了。
三、采用状态模式
1、代码
1.1 状态类
- 状态模式建议为对象的所有可能状态新建一个类, 每个状态独自实现自身状态下的各个行为。
// 抽象父类
public abstract class MediaPlayerState {
protected MediaPlayer mediaPlayer;
public MediaPlayerState(MediaPlayer mediaPlayer) {
this.mediaPlayer = mediaPlayer;
}
protected abstract void play();
protected abstract void pause();
protected abstract void stop();
}
// 播放状态
public class MediaPlayerPlayState extends MediaPlayerState {
public MediaPlayerPlayState(MediaPlayer mediaPlayer) {
super(mediaPlayer);
}
@Override
public void play() {
System.out.println("Media Player is already playing.");
mediaPlayer.setState(this);
}
@Override
public void pause() {
System.out.println("Media Player is now paused.");
mediaPlayer.setState(new MediaPlayerPauseState(mediaPlayer));
}
@Override
public void stop() {
System.out.println("Media Player is now stopped.");
mediaPlayer.setState(new MediaPlayerStopState(mediaPlayer));
}
}
// 暂停状态
public class MediaPlayerPauseState extends MediaPlayerState {
public MediaPlayerPauseState(MediaPlayer mediaPlayer) {
super(mediaPlayer);
}
@Override
public void play() {
System.out.println("Media Player resumed playing.");
mediaPlayer.setState(new MediaPlayerPlayState(mediaPlayer));
}
@Override
public void pause() {
System.out.println("Media Player is not playing.");
mediaPlayer.setState(this);
}
@Override
public void stop() {
System.out.println("Media Player is now stopped.");
mediaPlayer.setState(new MediaPlayerStopState(mediaPlayer));
}
}
// 停止状态
public class MediaPlayerStopState extends MediaPlayerState {
public MediaPlayerStopState(MediaPlayer mediaPlayer) {
super(mediaPlayer);
}
@Override
public void play() {
System.out.println("Media Player is now playing.");
mediaPlayer.setState(new MediaPlayerPlayState(mediaPlayer));
}
@Override
public void pause() {
System.out.println("Media Player is not playing.");
mediaPlayer.setState(new MediaPlayerPauseState(mediaPlayer));
}
@Override
public void stop() {
System.out.println("Media Player is now stopped.");
mediaPlayer.setState(this);
}
}
1.2 上下文(这里指:媒体播放器)
public class MediaPlayer {
private MediaPlayerState state;
public void setState(MediaPlayerState state) {
this.state = state;
}
public MediaPlayer() {
this.state = new MediaPlayerStopState(this);
}
public void play() {
state.play();
}
public void pause() {
state.pause();
}
public void stop() {
state.stop();
}
}
1.3 客户端
public class Main {
public static void main(String[] args) {
MediaPlayer mediaPlayer = new MediaPlayer();
System.out.println("Initial state: Stopped");
mediaPlayer.play(); // Stopped -> Playing
mediaPlayer.pause(); // Playing -> Paused
mediaPlayer.play(); // Paused -> Playing
mediaPlayer.stop(); // Playing -> Stopped
}
}
/*
Initial state: Stopped
Media Player is now playing.
Media Player is now paused.
Media Player resumed playing.
Media Player is now stopped.
*/
2、优点
- 每个状态类,只需要关心自己状态下,每种操作应该执行哪些逻辑就好了。
- 如果新增了状态,那无非就是新增一个类,其他已有的状态类,无非就是新写一个方法。这并不会去修改已有的代码,符合开闭原则。
上面的写法有一个小瑕疵,每个状态类实际上应该是单例模式(详见:对单例模式的饿汉式、懒汉式的思考),而不是每次切换状态的时候新建一个对象。
不同状态下,有些操作是一样的,例如:停止操作。这时候可以采用组合的方式进行复用。将停止操作放到一个类中,例如:MediaPlayerStopHandler。每个方法调用MediaPlayerStopHandler的stop方法()实现停止。
仔细看下状态模式的结构:
会发现和策略模式的结构很像。但二者有很多不同:
(1)策略模式:
- 1)根据客户端的输入,匹配一种策略。每个策略完全独立,策略A感知不到策略B。
- 2)策略A和策略B是做同一件事情,仅仅是做法不同。
(2)状态模式:
- 1)每种状态下,会有多种行为。(如果只有一种行为,也不存在状态转换了)
- 2)状态A和状态B不是完全独立的,状态A执行某个行为后,会从状态A转移到状态B。