はじめに
StatePatternについて説明します。
StatePattern
StatePatternは次の課題を扱います。
- 状態が変化したときにobjectの振る舞いを変更する。
- すでにある状態の振る舞いを変更することなく、新しい状態を追加する。
State patternのstructureを示します。
Contextは状態をあらわすstateをもちます。
Context:requestはstate.handle()を呼び出します。
IStateはStateのinterfaceを提供します。
State1 / State2 はIStateのinterfaceを実装します。
状態遷移の方法についてはSample Codeを参照ください。
Sample Code1
次の状態遷移をするPlayerをState patternで実装します。
Playerは次の状態をもちます。
- Stopped
- Playing
play / stopのイベントにより状態が遷移します。
Playing状態に変化したとき、doPlayを実行します。
Stopped状態に変化したとき、doStopを実行します。
上記図に対応するPlantUMLは次の通りです。
plantuml
@startuml
[*] --> Stopped
Stopped --> Playing : play
Playing --> Stopped : stop
Stopped: entry / doStop
Playing: entry / doPlay
@enduml
Class図は以下の通りです。
Kotlinの実装は次の通りです。
test.kt
interface IPlayerState {
fun enter(player: Player) {}
fun play(player: Player) {}
fun stop(player: Player) {}
}
class StateStopped : IPlayerState {
override fun enter(player: Player) {
doStop()
}
override fun play(player: Player) {
println("play() : Stopped -> Playing")
player.changeState(StatePlaying())
}
private fun doStop() = println("doStop()")
}
class StatePlaying : IPlayerState {
override fun enter(player: Player) {
doPlay()
}
override fun stop(player: Player) {
println("stop() : Playing -> Stopped")
player.changeState(StateStopped())
}
private fun doPlay() = println("doPlay()")
}
class Player {
var state: IPlayerState = StateStopped()
fun changeState(next: IPlayerState) {
state = next
state.enter(this)
}
fun play() {
state.play(this)
}
fun stop() {
state.stop(this)
}
}
fun main() {
val player = Player()
player.play()
player.stop()
/*
play() : Stopped -> Playing
doPlay()
stop() : Playing -> Stopped
doStop()
*/
}
Sample Code2
Sample Code1にPaused状態を追加します。
状態遷移は次の通りです。
上記図に対応するPlantUMLは次の通りです。
plantuml
@startuml
[*] --> Stopped
Stopped --> Playing : play
Playing --> Stopped : stop
Playing --> Paused : pause
Paused --> Playing : play
Paused --> Stopped : stop
Stopped: entry / doStop
Playing: entry / doPlay
Paused: entry / doPause
@enduml
Kotlinの実装は次の通りです。
Sample Code1にたいして追加したところに "add" のコメントをつけます。
test.kt
interface IPlayerState {
fun enter(player: Player) {}
fun play(player: Player) {}
fun stop(player: Player) {}
fun pause(player: Player) {} // add
}
class StateStopped : IPlayerState {
override fun enter(player: Player) {
doStop()
}
override fun play(player: Player) {
println("play() : Stopped -> Playing")
player.changeState(StatePlaying())
}
private fun doStop() = println("doStop()")
}
class StatePlaying : IPlayerState {
override fun enter(player: Player) {
doPlay()
}
override fun stop(player: Player) {
println("stop() : Playing -> Stopped")
player.changeState(StateStopped())
}
// add start
override fun pause(player: Player) {
println("pause() : Playing -> Paused")
player.changeState(StatePaused())
}
// add end
private fun doPlay() = println("doPlay()")
}
// add start
class StatePaused : IPlayerState {
override fun enter(player: Player) {
doPause()
}
override fun play(player: Player) {
println("play() : Paused -> Playing")
player.changeState(StatePlaying())
}
override fun stop(player: Player) {
println("stop() : Paused -> Stopped")
player.changeState(StateStopped())
}
private fun doPause() = println("doPause()")
}
// add end
class Player {
var state: IPlayerState = StateStopped()
fun changeState(next: IPlayerState) {
state = next
state.enter(this)
}
fun play() {
state.play(this)
}
fun stop() {
state.stop(this)
}
// add start
fun pause() {
state.pause(this)
}
// add end
}
fun main() {
val player = Player()
player.play()
player.pause() // add
player.play() // add
player.stop()
/*
play() : Stopped -> Playing
doPlay()
pause() : Playing -> Paused
doPause()
play() : Paused -> Playing
doPlay()
stop() : Playing -> Stopped
doStop()
*/
}
Sample Code3
Sample Code2をenumで書き換えてみます。
test.kt
class Player {
enum class State {
STOPPED {
override fun enter(player: Player) {
player.doStop()
}
override fun play(player: Player) {
println("play() : Stopped -> Playing")
player.changeState(PLAYING)
}
},
PLAYING {
override fun enter(player: Player) {
player.doPlay()
}
override fun stop(player: Player) {
println("stop() : Playing -> Stopped")
player.changeState(STOPPED)
}
override fun pause(player: Player) {
println("pause() : Playing -> Paused")
player.changeState(PAUSED)
}
},
PAUSED {
override fun enter(player: Player) {
player.doPause()
}
override fun play(player: Player) {
println("play() : Paused -> Playing")
player.changeState(PLAYING)
}
override fun stop(player: Player) {
println("stop() : Paused -> Stopped")
player.changeState(STOPPED)
}
};
open fun enter(player: Player) {}
open fun play(player: Player) {}
open fun stop(player: Player) {}
open fun pause(player: Player) {}
}
private var state: State = State.STOPPED
fun changeState(next: State) {
state = next
state.enter(this)
}
fun play() {
state.play(this)
}
fun doPlay() {
println("doPlay()")
}
fun stop() {
state.stop(this)
}
fun doStop() {
println("doStop()")
}
fun pause() {
state.pause(this)
}
fun doPause() {
println("doPause()")
}
}
fun main() {
val player = Player()
player.play()
player.pause()
player.play()
player.stop()
/*
play() : Stopped -> Playing
doPlay()
pause() : Playing -> Paused
doPause()
play() : Paused -> Playing
doPlay()
stop() : Playing -> Stopped
doStop()
*/
}