容许一个对象在其内部状态改变时改变它的行为。对象看起来彷佛修改了它的类java
状态模式与有限状态机的概念紧密相关。其主要思想是程序在任意时刻仅可处于几种有限的状态中。在任何一个特定状态中,程序的行为都不相同,且可瞬间从一个状态切换到另外一个状态。不过,根据当前状态,程序可能会切换到另一种状态,也可能会保持当前状态不变。这些数量有限且预先定义的状态切换规则被称为转移 ide
假如你有一个 文档
Document类。文档可能会处于草稿
Draft 、 审阅中
Moderation和 已发布
Published三种状态中的一种。文档的 publish
发布方法在不一样状态下的行为略有不一样:fetch
草稿
状态时,它会将文档转移到审阅中状态审阅中
状态时,若是当前用户是管理员,会公开发布文档已发布
状态时,它不会进行任何操做
状态机一般由众多条件运算符( if
或 switch
)实现,可根据对象的当前状态选择相应的行为。“状态” 一般只是对象中的一组成员变量值。以下伪码所示:ui
class Document is field state: string // ... method publish() is switch (state) "draft": state = "moderation" break "moderation": if (currentUser.role == 'admin') state = "published" break "published": // 什么也不作。 break // ...
当咱们逐步在文档类中添加更多状态和依赖于状态的行为后,基于条件语句的状态机就会暴露其最大的弱点。为了能根据当前状态选择完成相应行为的方法,绝大部分方法中会包含复杂的条件语句。修改其转换逻辑可能会涉及到修改全部方法中的状态条件语句,致使代码的维护工做很是艰难。这个问题会随着项目进行变得愈加严重。咱们很难在设计阶段预测到全部可能的状态和转换。随着时间推移,最初仅包含有限条件语句的简洁状态机可能会变的臃肿而难以维护。 this
状态模式建议为对象的全部可能状态新建一个类,而后将全部状态的对应行为抽取到这些类中。原始对象被称为上下文(context),它并不会自行实现全部行为,而是会保存一个指向表示当前状态的状态对象的引用,且将全部与状态相关的工做委派给该对象spa
如需将上下文转换为另一种状态,则需将当前活动的状态对象替换为另一个表明新状态的对象。采用这种方式是有前提的:全部状态类都必须遵循一样的接口,并且上下文必须仅经过接口与这些对象进行交互。这个结构可能看上去与策略模式类似,但有一个关键性的不一样——在状态模式中,特定状态知道其余全部状态的存在,且能触发从一个状态到另外一个状态的转换;策略则几乎彻底不知道其余策略的存在设计
1. 经过消除臃肿的状态机条件语句简化上下文代码code
2. 将与特定状态相关的行为局部化,而且将不一样状态的行为分割开来(单一职责原则)对象
3. 无需修改已有状态类和上下文就能引入新状态(开闭原则)blog
4. State对象可被共享 各Context对象能够共享一个State对象。当状态以这种方式被共享时,他们必然是没有内部状态而只有行为的轻量级对象(Flyweight)
本例中,状态模式容许媒体播放器根据当前的回放状态进行不一样的控制行为。播放器主类包含一个指向状态对象的引用,它将完成播放器的绝大部分工做。某些行为可能会用一个状态对象替换另外一个状态对象,改变播放器对用户交互的回应方式。
states/State.java: 通用状态接口
package state.states; import state.ui.Player; /** * @author GaoMing * @date 2021/7/26 - 8:02 */ public abstract class State { Player player; /** * Context passes itself through the state constructor. This may help a * state to fetch some useful context data if needed. */ State(Player player) { this.player = player; } public abstract String onLock(); public abstract String onPlay(); public abstract String onNext(); public abstract String onPrevious(); }
states/LockedState.java
package state.states; import state.ui.Player; /** * @author GaoMing * @date 2021/7/26 - 8:04 * Concrete states provide the special implementation for all interface methods. */ public class LockedState extends State{ LockedState(Player player) { super(player); player.setPlaying(false); } @Override public String onLock() { if (player.isPlaying()) { player.changeState(new ReadyState(player)); return "Stop playing"; } else { return "Locked..."; } } @Override public String onPlay() { player.changeState(new ReadyState(player)); return "Ready"; } @Override public String onNext() { return "Locked..."; } @Override public String onPrevious() { return "Locked..."; } }
states/ReadyState.java
package state.states; import state.ui.Player; /** * @author GaoMing * @date 2021/7/26 - 8:06 * They can also trigger state transitions in the context. */ public class ReadyState extends State{ public ReadyState(Player player) { super(player); } @Override public String onLock() { player.changeState(new LockedState(player)); return "Locked..."; } @Override public String onPlay() { String action = player.startPlayback(); player.changeState(new PlayingState(player)); return action; } @Override public String onNext() { return "Locked..."; } @Override public String onPrevious() { return "Locked..."; } }
states/PlayingState.java
package state.states; import state.ui.Player; /** * @author GaoMing * @date 2021/7/26 - 8:06 */ public class PlayingState extends State{ PlayingState(Player player) { super(player); } @Override public String onLock() { player.changeState(new LockedState(player)); player.setCurrentTrackAfterStop(); return "Stop playing"; } @Override public String onPlay() { player.changeState(new ReadyState(player)); return "Paused..."; } @Override public String onNext() { return player.nextTrack(); } @Override public String onPrevious() { return player.previousTrack(); } }
ui/Player.java: 播放器的主要代码
package state.ui; import state.states.ReadyState; import state.states.State; import java.util.ArrayList; import java.util.List; /** * @author GaoMing * @date 2021/7/26 - 8:03 */ public class Player { private State state; private boolean playing = false; private List<String> playlist = new ArrayList<>(); private int currentTrack = 0; public Player() { this.state = new ReadyState(this); setPlaying(true); for (int i = 1; i <= 12; i++) { playlist.add("Track " + i); } } public void changeState(State state) { this.state = state; } public State getState() { return state; } public void setPlaying(boolean playing) { this.playing = playing; } public boolean isPlaying() { return playing; } public String startPlayback() { return "Playing " + playlist.get(currentTrack); } public String nextTrack() { currentTrack++; if (currentTrack > playlist.size() - 1) { currentTrack = 0; } return "Playing " + playlist.get(currentTrack); } public String previousTrack() { currentTrack--; if (currentTrack < 0) { currentTrack = playlist.size() - 1; } return "Playing " + playlist.get(currentTrack); } public void setCurrentTrackAfterStop() { this.currentTrack = 0; } }
ui/UI.java: 播放器的 GUI
package state.ui; import javax.swing.*; import java.awt.*; /** * @author GaoMing * @date 2021/7/26 - 8:07 */ public class UI { private Player player; private static JTextField textField = new JTextField(); public UI(Player player) { this.player = player; } public void init() { JFrame frame = new JFrame("Test player"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel context = new JPanel(); context.setLayout(new BoxLayout(context, BoxLayout.Y_AXIS)); frame.getContentPane().add(context); JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER)); context.add(textField); context.add(buttons); // Context delegates handling user's input to a state object. Naturally, // the outcome will depend on what state is currently active, since all // states can handle the input differently. JButton play = new JButton("Play"); play.addActionListener(e -> textField.setText(player.getState().onPlay())); JButton stop = new JButton("Stop"); stop.addActionListener(e -> textField.setText(player.getState().onLock())); JButton next = new JButton("Next"); next.addActionListener(e -> textField.setText(player.getState().onNext())); JButton prev = new JButton("Prev"); prev.addActionListener(e -> textField.setText(player.getState().onPrevious())); frame.setVisible(true); frame.setSize(300, 100); buttons.add(play); buttons.add(stop); buttons.add(next); buttons.add(prev); } }
Demo.java: 客户端代码
package state; import state.ui.Player; import state.ui.UI; /** * @author GaoMing * @date 2021/7/26 - 8:08 */ public class Demo { public static void main(String[] args) { Player player = new Player(); UI ui = new UI(player); ui.init(); } }
运行结果
实现State模式时的考虑:
1) 谁定义状态转换? State模式不指定哪一个参与者定义状态转换准则。若是该准则是固定的,那么它们可在Context中彻底实现。而后若让State子类自身指定它们的后继者状态以及什么时候进行转换,一般更加灵活。更合适。这须要Context增长一个接口,让State对象显式的设定Context的当前状态。用这种方法分散转换逻辑能够很容易地定义新的State子类来修改和扩展该逻辑。这样作有一个缺点,一个State子类至少拥有一个其余子类的信息,这就在各子类之间产生了依赖
2) 建立和销毁State对象 什么时候建立State对象?以及什么时候销毁他们? 是须要时再建立State对象,并随后销毁他们仍是,提早建立它们而且始终不销毁它们。当要进入的状态是运行时不可知的,而且上下文不常常改变时,用第一种较为合适。若是State对象存储了大量信息,当状态改变很频繁时,第二种方法较好
使用示例:在Java语言中,状态模式一般被用于将基于switch语句的大型状态机转换为对象 核心Java程序库中一些状态模式的示例: javax.faces.lifecycle.LifeCycle#execute() (由 FacesServlet控制:行为依赖于当前 JSF 生命周期的阶段 (状态)) 识别方法: 方法受外部控制且能根据对象状态改变行为