LoginSignup
0
0

More than 3 years have passed since last update.

Design pattern - StatePatternを理解する

Last updated at Posted at 2021-01-30

はじめに

StatePatternについて説明します。

StatePattern

StatePatternは次の課題を扱います。

  • 状態が変化したときにobjectの振る舞いを変更する。
  • すでにある状態の振る舞いを変更することなく、新しい状態を追加する。

State patternのstructureを示します。

test-StatePattern1.jpg

Contextは状態をあらわすstateをもちます。
Context:requestはstate.handle()を呼び出します。

IStateはStateのinterfaceを提供します。
State1 / State2 はIStateのinterfaceを実装します。

状態遷移の方法についてはSample Codeを参照ください。

Sample Code1

次の状態遷移をするPlayerをState patternで実装します。

test.png

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図は以下の通りです。

test-StatePattern2.png

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状態を追加します。
状態遷移は次の通りです。

test.png

上記図に対応する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()
     */
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0