状态模式(行为型模式)
模式概述
状态模式是一种行为设计模式,它可以让一个对象在内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
问题
状态模式与有限状态机的概念紧密相关,其主要思想如下。
程序在任意时刻仅可处于几种有限的状态中。
任何一个特定状态程序的行为都不相同。
程序可瞬间从一个状态切换到另一个状态。

方案
状态模式建议为对象的每一个可能状态都新建一个类,然后将所有状态的对应行为抽取到这些类中。
它看上去可能与策略模式
相似,但有一个关键性的不同。
状态模式
的特定状态知道其他所有状态的存在,且能触发从一个状态到另一个状态的转换。策略模式
则几乎完全不知道其他策略的存在,而且也无法切换。
结构

实现
Java
/**
* 状态抽象类
*
*/
public abstract class State {
Player player;
State(Player player) {
this.player = player;
}
public abstract String onLock();
public abstract String onPlay();
public abstract String onNext();
public abstract String onPrevious();
}
/**
* 锁定状态
*
*/
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...";
}
}
/**
* 就绪状态
*
*/
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...";
}
}
/**
* 播放状态
*
*/
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();
}
}
/**
* 播放器
*
*/
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 "正在播放 " + playlist.get(currentTrack);
}
public String nextTrack() {
currentTrack++;
if (currentTrack > playlist.size() - 1) {
currentTrack = 0;
}
return "正在播放 " + playlist.get(currentTrack);
}
public String previousTrack() {
currentTrack--;
if (currentTrack < 0) {
currentTrack = playlist.size() - 1;
}
return "正在播放 " + playlist.get(currentTrack);
}
public void setCurrentTrackAfterStop() {
this.currentTrack = 0;
}
}
/**
* 播放器的GUI
*
*/
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);
JButton play = new JButton("播放");
play.addActionListener(e -> textField.setText(player.getState().onPlay()));
JButton stop = new JButton("暂停");
stop.addActionListener(e -> textField.setText(player.getState().onLock()));
JButton next = new JButton("下一首");
next.addActionListener(e -> textField.setText(player.getState().onNext()));
JButton prev = new JButton("上一首");
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);
}
}
/**
* 客户端
*
*/
public class Client {
public static void main(String[] args) {
Player player = new Player();
UI ui = new UI(player);
ui.init();
}
}
Go
package main
import "fmt"
/**
* 无人售货机
*
*/
type VendingMachine struct {
hasItem State
itemRequested State
hasMoney State
noItem State
currentState State
itemCount int
itemPrice int
}
func newVendingMachine(itemCount, itemPrice int) *VendingMachine {
v := &VendingMachine{
itemCount: itemCount,
itemPrice: itemPrice,
}
hasItemState := &HasItemState{
vendingMachine: v,
}
itemRequestedState := &ItemRequestedState{
vendingMachine: v,
}
hasMoneyState := &HasMoneyState{
vendingMachine: v,
}
noItemState := &NoItemState{
vendingMachine: v,
}
v.setState(hasItemState)
v.hasItem = hasItemState
v.itemRequested = itemRequestedState
v.hasMoney = hasMoneyState
v.noItem = noItemState
return v
}
func (v *VendingMachine) requestItem() error {
return v.currentState.requestItem()
}
func (v *VendingMachine) addItem(count int) error {
return v.currentState.addItem(count)
}
func (v *VendingMachine) insertMoney(money int) error {
return v.currentState.insertMoney(money)
}
func (v *VendingMachine) dispenseItem() error {
return v.currentState.dispenseItem()
}
func (v *VendingMachine) setState(s State) {
v.currentState = s
}
func (v *VendingMachine) incrementItemCount(count int) {
fmt.Printf("Adding %d items\n", count)
v.itemCount = v.itemCount + count
}
/**
* 状态接口
*
*/
type State interface {
addItem(int) error
requestItem() error
insertMoney(money int) error
dispenseItem() error
}
/**
* 无商品时的状态
*
*/
type NoItemState struct {
vendingMachine *VendingMachine
}
func (i *NoItemState) requestItem() error {
return fmt.Errorf("商品缺货")
}
func (i *NoItemState) addItem(count int) error {
i.vendingMachine.incrementItemCount(count)
i.vendingMachine.setState(i.vendingMachine.hasItem)
return nil
}
func (i *NoItemState) insertMoney(money int) error {
return fmt.Errorf("商品缺货")
}
func (i *NoItemState) dispenseItem() error {
return fmt.Errorf("商品缺货")
}
/**
* 有商品时的状态
*
*/
type HasItemState struct {
vendingMachine *VendingMachine
}
func (i *HasItemState) requestItem() error {
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
return fmt.Errorf("不存在商品")
}
fmt.Printf("Item requestd\n")
i.vendingMachine.setState(i.vendingMachine.itemRequested)
return nil
}
func (i *HasItemState) addItem(count int) error {
fmt.Printf("%d 已添加商品\n", count)
i.vendingMachine.incrementItemCount(count)
return nil
}
func (i *HasItemState) insertMoney(money int) error {
return fmt.Errorf("请先选择商品")
}
func (i *HasItemState) dispenseItem() error {
return fmt.Errorf("请先选择商品")
}
/**
* 请求商品状态
*
*/
type ItemRequestedState struct {
vendingMachine *VendingMachine
}
func (i *ItemRequestedState) requestItem() error {
return fmt.Errorf("已请求商品")
}
func (i *ItemRequestedState) addItem(count int) error {
return fmt.Errorf("正在分发商品")
}
func (i *ItemRequestedState) insertMoney(money int) error {
if money < i.vendingMachine.itemPrice {
return fmt.Errorf("钱不够,得加钱 %d", i.vendingMachine.itemPrice)
}
fmt.Println("钱够了")
i.vendingMachine.setState(i.vendingMachine.hasMoney)
return nil
}
func (i *ItemRequestedState) dispenseItem() error {
return fmt.Errorf("请先给钱")
}
/**
* 收钱状态
*
*/
type HasMoneyState struct {
vendingMachine *VendingMachine
}
func (i *HasMoneyState) requestItem() error {
return fmt.Errorf("正在分发商品")
}
func (i *HasMoneyState) addItem(count int) error {
return fmt.Errorf("正在分发商品")
}
func (i *HasMoneyState) insertMoney(money int) error {
return fmt.Errorf("商品缺货")
}
func (i *HasMoneyState) dispenseItem() error {
fmt.Println("分配商品")
i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
if i.vendingMachine.itemCount == 0 {
i.vendingMachine.setState(i.vendingMachine.noItem)
} else {
i.vendingMachine.setState(i.vendingMachine.hasItem)
}
return nil
}
/**
* 客户端
*
*/
func main() {
vendingMachine := newVendingMachine(1, 10)
err := vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println()
err = vendingMachine.addItem(2)
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println()
err = vendingMachine.requestItem()
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.insertMoney(10)
if err != nil {
log.Fatalf(err.Error())
}
err = vendingMachine.dispenseItem()
if err != nil {
log.Fatalf(err.Error())
}
}
适用场景
如果某个类需要根据成员变量的当前值改变自身行为,且需要使用大量的条件语句时,可以使用状态模式。
对象需要根据自身当前状态实现不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可以使用状态模式。
当相似状态和基于条件的状态机转换中存在许多重复代码时,可以使用状态模式。
优缺点
状态模式的优点。
将与特定状态相关的代码放在单独的类中,符合
单一职责原则
。无需修改已有状态类和上下文就能引入新状态,符合
开闭原则
。通过消除臃肿的状态机条件语句简化上下文代码。
状态模式的缺点。
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
如果状态的数量很少,或者状态很少发生改变,那么使用状态模式反而会让代码更复杂。
相关性
桥接模式
、状态模式
、策略模式
和适配器模式
较为相似,它们都基于组合模式
机制——将工作委派给其他对象来解决问题。状态模式
可被视为策略模式
的扩展,两者都基于组合机制:都通过将部分工作委派给其他对象来改变其行为。策略模式
使得这些对象之间完全独立,它们不知道其他对象的存在;但状态模式
模式不限制具体状态之间的依赖,允许它们自行改变在不同上下文中的状态。
感谢支持
更多内容,请移步《超级个体》。