您也可以将此方法应用于对象。想象一下,我们有一个 您也可以将此方法应用于对象。想象一下,我们有一个 Document 类。文档可以处于以下三种状态之一: Draft、Moderation 和 Published 。publish 方法在每种状态下的工作方式略有不同:
在Draft中,它会将文档移动到审核状态。
在Moderation中,它使文档公开,但前提是当前用户是管理员。
在Published中,它根本不做任何事情。
文档对象的可能状态和转换。
状态机通常使用大量条件语句( if 或switch )实现,这些条件语句根据对象的当前状态选择适当的行为。通常,此“状态”只是对象字段的一组值。即使你以前从未听说过有限状态机,你也可能至少实现过一次状态。以下代码结构是否敲响了警钟? )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
classDocument is field state: string // ... method publish() is switch(state) "draft": state = "moderation" break "moderation": if (currentUser.role == "admin") state = "published" break "published": // Do nothing. break // ...
// The AudioPlayer class acts as a context. It also maintains a // reference to an instance of one of the state classes that // represents the current state of the audio player. classAudioPlayer is field state: State field UI, volume, playlist, currentSong
constructor AudioPlayer() is this.state = newReadyState(this)
// Context delegates handling user input to a state // object. Naturally, the outcome depends on what state // is currently active, since each state can handle the // input differently. UI = newUserInterface() UI.lockButton.onClick(this.clickLock) UI.playButton.onClick(this.clickPlay) UI.nextButton.onClick(this.clickNext) UI.prevButton.onClick(this.clickPrevious)
// Other objects must be able to switch the audio player's // active state. method changeState(state: State) is this.state = state
// UI methods delegate execution to the active state. method clickLock() is state.clickLock() method clickPlay() is state.clickPlay() method clickNext() is state.clickNext() method clickPrevious() is state.clickPrevious()
// A state may call some service methods on the context. method startPlayback() is // ... method stopPlayback() is // ... method nextSong() is // ... method previousSong() is // ... method fastForward(time) is // ... method rewind(time) is // ...
// The base state class declares methods that all concrete // states should implement and also provides a backreference to // the context object associated with the state. States can use // the backreference to transition the context to another state. abstractclassState is protected field player: AudioPlayer
// Context passes itself through the state constructor. This // may help a state fetch some useful context data if it's // needed. constructor State(player) is this.player = player
// Concrete states implement various behaviors associated with a // state of the context. classLockedStateextendsState is
// When you unlock a locked player, it may assume one of two // states. method clickLock() is if(player.playing) player.changeState(newPlayingState(player)) else player.changeState(newReadyState(player))
method clickPlay() is // Locked, so do nothing.
method clickNext() is // Locked, so do nothing.
method clickPrevious() is // Locked, so do nothing.
// They can also trigger state transitions in the context. classReadyStateextendsState is method clickLock() is player.changeState(newLockedState(player))
method clickPlay() is player.startPlayback() player.changeState(newPlayingState(player))
method clickNext() is player.nextSong()
method clickPrevious() is player.previousSong()
classPlayingStateextendsState is method clickLock() is player.changeState(newLockedState(player))
method clickPlay() is player.stopPlayback() player.changeState(newReadyState(player))
method clickNext() is if(event.doubleclick) player.nextSong() else player.fastForward(5)
method clickPrevious() is if(event.doubleclick) player.previous() else player.rewind(5)
Applicability 适用性
如果对象的行为根据其当前状态而有所不同,状态数量巨大,并且特定于状态的代码经常更改,请使用 State 模式。
from __future__ import annotations from abc import ABC, abstractmethod
classContext: """ The Context defines the interface of interest to clients. It also maintains a reference to an instance of a State subclass, which represents the current state of the Context. """
_state = None """ A reference to the current state of the Context. """
deftransition_to(self, state: State): """ The Context allows changing the State object at runtime. """
print(f"Context: Transition to {type(state).__name__}") self._state = state self._state.context = self
""" The Context delegates part of its behavior to the current State object. """
defrequest1(self): self._state.handle1()
defrequest2(self): self._state.handle2()
classState(ABC): """ The base State class declares methods that all Concrete State should implement and also provides a backreference to the Context object, associated with the State. This backreference can be used by States to transition the Context to another State. """
""" Concrete States implement various behaviors, associated with a state of the Context. """
classConcreteStateA(State): defhandle1(self) -> None: print("ConcreteStateA handles request1.") print("ConcreteStateA wants to change the state of the context.") self.context.transition_to(ConcreteStateB())
defhandle2(self) -> None: print("ConcreteStateB handles request2.") print("ConcreteStateB wants to change the state of the context.") self.context.transition_to(ConcreteStateA())
Context: Transition to ConcreteStateA ConcreteStateA handles request1. ConcreteStateA wants to change the state of the context. Context: Transition to ConcreteStateB ConcreteStateB handles request2. ConcreteStateB wants to change the state of the context. Context: Transition to ConcreteStateA
/// A music player holds a playlist and it can do basic operations over it. pubstructPlayer { playlist: Vec<Track>, current_track: usize, _volume: u8, }
/// There is a base `State` trait with methods `play` and `stop` which make /// state transitions. There are also `next` and `prev` methods in a separate /// `impl dyn State` block below, those are default implementations /// that cannot be overridden. /// /// What is the `self: Box<Self>` notation? We use the state as follows: /// ```rust /// let prev_state = Box::new(PlayingState); /// let next_state = prev_state.play(&mut player); /// ``` /// A method `play` receives a whole `Box<PlayingState>` object, /// and not just `PlayingState`. The previous state "disappears" in the method, /// in turn, it returns a new `Box<PausedState>` state object. pubtraitState { fnplay(self: Box<Self>, player: &mut Player) ->Box<dyn State>; fnstop(self: Box<Self>, player: &mut Player) ->Box<dyn State>; fnrender(&self, player: &Player, view: &mut TextView); }
// Here is how state mechanics work: the previous state // executes an action and returns a new state. // Each state has all 4 operations but reacts differently. state = match button { "Play" => state.play(&mut player), "Stop" => state.stop(&mut player), "Prev" => state.prev(&mut player), "Next" => state.next(&mut player), _ => unreachable!(), };
state.render(&player, &mut view);
s.set_user_data(PlayerApplication { player, state }); }