Stateパターンのアンチパターンを考える

# はじめに
Stateパターンの**アンチパターン**をいくつか考えたので紹介します．

# Stateパターンとは
GoFのデザパタのあれです．
ここでは解説しません．

https://ja.wikipedia.org/wiki/State_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3#:~:text=State%20%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%88%E8%8B%B1%3A%20state%20pattern,%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%81%AA%E6%89%8B%E6%AE%B5%E3%81%A8%E3%81%AA%E3%82%8B%E3%80%82

# 本題
## 他Stateへの依存
状態遷移を行う際，Stateクラス内部から**次のStateを指定**する必要があります．
しかし肝心のStateはどこから持ってくれば良いのでしょうか？
### Singleton
Stateパターンについて調べるとよく出てくるのが，Stateクラスを`Singleton`にしてしまう方法です．

この方法は，Stateクラスが内部状態を持つ場合，複数の`Context`が利用できなくなってしまいます．
複数の`Context`は，ゲームプログラミングで例を考えるとモンスターやプレイヤーなどのStateでしょうか．

また，後述しますがStateクラスが他のStateクラスにアクセスするのはあまりよろしくありません．

### 新規生成
`Singleton`のデメリットである，複数の`Context`で利用できない，が解決出来ます．

しかし，遷移ごとにインスタンス化されるというのは，**パフォーマンス上あまりよくなさそう**です．
同一`Context`内ならオブジェクトを再利用したいところです．

さらに，新規生成するStateクラスの依存クラスに**自分自身も依存してしまう**ことになります．

### DI
だったら`Context`から次のStateを受け取ればいいのではないでしょうか？

これで`Context`内でStateクラスが`Singleton`のように振る舞うようになり，再利用が可能になりました．
また不要な依存クラスができてしまう問題も解決できそうです．

ただし，Stateがループする場合，**コンストラクタインジェクションが不可能**になるため，工夫が必要です．

### これで解決なのか？
`Singleton`のところで少し触れましたが，実は上記の方法ではもう一つ問題が残っています．

### 複雑な依存関係
遷移時に`Context`へStateインスタンスを伝えるということは，そのクラスに依存していることになります．
つまり，**遷移=依存**になります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758030/bba27b85-aff8-0643-7dcf-e90089d983b8.png)

遷移先として指定しているだけとは言え，依存関係が滅茶苦茶な**立派なスパゲッティ状態**です．

ちなみに，`抽象StateをDIすればいいんじゃないの？`と思う方もいるかもしれません．
確かに依存関係はクリーンになります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758030/fbfb4d4f-825c-8bca-906e-9aafb2c00b7d.png)
しかしその場合，State遷移の裁量権が`Context`に移ることになります．
`Context`がStateを注入するためです．**Stateパターンとは？** 

(遷移先が動的に入れ替わる，みたいなことをしたいならありかも)

あと，遷移先を`IStateA`のように抽象化する方法も思いつきましたが，そもそも**他のStateクラスを操作できる**時点でダメです．

### 解決方法
ここまでの方法で問題の根本的な原因は，**Stateの種類とStateクラスそのものが一緒になっている**ことです．

`StateA -> StateB`という遷移を考えましょう．StateAは`Context`に，**StateBに遷移することを伝えたい**のですが，このときStateAにとってStateBは，`StateB`という**種類(Type)が欲しい**のであり，StateBそのものはどうだっていいんです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758030/7bbd8f29-aa22-47d4-aac7-76df9a9c8b38.png)

つまりどうすればいいのか．
StateTypeとしてStateの型を`Enum`にしてしまいましょう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/758030/d9c14f86-eecb-36ad-a384-edb64cc678f2.png)

これで全てうまくいきます(多分)．

ちなみにStateTypeとStateインスタンスの紐づけは`ContextImpl`が行います．
しかし，これを聞いて１つ疑問が生まれるかもしれません．

**いやいや...結局State遷移の裁量権はContextが持ってんじゃないの？**

そうかもしれません．
でも，**StateType.Aを伝えてるのにStateBに遷移する**っておかしな話じゃないですか？

この場合は**Stateクラス自身が遷移先を明示している**ので，いいんじゃないかなと思います．


## 各Stateの振る舞い
Stateパターンの本命と言える部分です．
しかし，ここに**大きなアンチパターンを発見**しました．
ここでは，ゲームを例に考えていきます．

### 話にならない実装例

Stateパターンと検索すると，このような実装例が出てきがちです．

```Kotlin
class Player {

    private var currentState: IState

    fun Damage() {
        currentState.onDamage()
    }

}

interface IState {

    fun onDamage()

}

class StateNormal : IState {

    override fun onDamage() {
        println("いてて..")
    }

}

class StateInvincible : IState {

    override fun onDamage() {
        println("無敵だもん！")
    }

}
```

**「PlayerクラスはIStateのonDamageを呼ぶだけで場合分けをしなくていいね！」**
**「ポリモーフィズムだね！」**

...

Stateパターン実装してみれば分かります．実際のところ，

* プレイヤーのHP
* 何の攻撃を受けたか
* ダメージの演出
* 無敵時間の処理

など，**沢山の要素への依存**があり，一筋縄ではいかないです．


### 命令的に書くな
Stateパターンと言えば、何らかのイベント(onDamageなど)に対して，`Context`のメソッドを呼ぶ実装方法をよく見かけます．
しかしこれは，**アンチパターンになり得る**と思っています．

まずやりがちな実装を見てみましょう．先程の例をもう少し実践的にして考えます．

* NormalState，FixedStateではダメージを受けるとHPが減り，数秒間無敵状態になる
* FixedStateではノックバックはしない
* InvincibleStateではHPは減らないがノックバックだけする

これらを満たすStateクラスたちを実装してみます．

```Kotlin
interface PlayerContext {
    var hp: Int
    var mp: Int
    var invincibleTime: Double
    var velocity: Vector
    fun changeStateTo(next: StateType)
}

class StateNormal(private val context: PlayerContext) : IState {
    
    fun onDamage() {
        // 体力を減らす
        context.hp -= 10
        // ノックバック
        context.velocity = Vector(1.0, 1.0, 1.0)
        // 無敵時間
        context.invincibleTime = 10.0
        context.changeStateTo(StateType.Invincible)
    }

}
class StateFixed(private val context: PlayerContext) : IState {
    
    fun onDamage() {
        // 体力を減らす
        context.hp -= 10
        // 無敵時間
        context.invincibleTime = 10.0
        context.changeStateTo(StateType.Invincible)
    }

}
class StateInvincible(private val context: PlayerContext) : IState {
    
    fun onDamage() {
        // ノックバック
        context.velocity = Vector(1.0, 1.0, 1.0)
    }

}
```

一見すると，いい感じのStateパターンになってるように思えます．
~~(PlayerContextが全然カプセル化されてないですが)~~

しかし，**Stateクラスの振る舞いは宣言的であるべき**とぼくは考えています．
すなわち，**Contextに対して複雑な処理をすべきではない**ということです．

### 責任が増える
上の例でStateクラスの責務をよく考えてみましょう．
ダメージを受けたときに「HPが減るか」「無敵状態になるか」「ノックバックするか」

**これらの宣言だけをさせたかったんです**．

しかしながら，「HPの減少量」「無敵時間の長さ」「ノックバック力」など
**Stateクラスにとってどうでもいいこと**まで把握してしまっています．


より実践的なものなら，引数から「ダメージ量」「攻撃方向」などの情報を受け取って，「プレイヤーの装備」「防御力」からHP減少量の計算を行ったりするかもしれません．

これらは**Stateクラスが果たすべき責務**ではありませんよね．

### 宣言的に
今度は，Stateクラスが宣言的になるよう実装してみます．
```Kotlin
interface IState {
    
    val knockBackOnDamage: Boolean
    val isDamageable: Boolean
    
}

class StateNormal() : IState {

    override val knockBackOnDamage: Boolean = true
    override val isDamageable: Boolean = true
    
}

class StateFixed() : IState {

    override val knockBackOnDamage: Boolean = false
    override val isDamageable: Boolean = true

}

class StateInvincible() : IState {

    override val knockBackOnDamage: Boolean = true
    override val isDamageable: Boolean = false

}
```
Stateを利用する側は次のようになります．
```Kotlin
class Player {
    
    private var state: IState
    private var hp: Int
    private var velocity: Vector
    
    fun damage() {
        
        if (state.isDamageable) {
            
            hp -= 10
            
        }
        
        if (state.knockBackOnDamage) {
            
            velocity = Vector(1.0, 1.0, 1.0)
            
        }
        
    }
    
}
```
これで，Stateクラスの振る舞いを宣言的にすることができました．

また，**「Stateごとにダメージ量やらノックバック力を変えたい」**
となった場合でも，Stateの振る舞いに「ダメージ倍率」なり「ダメージ計算メソッド」なりを付け加えてやればOKです．

# 最後に
## 本質はStateパターンじゃない
ぼくはこの記事でStateパターンはこうだ！といいたいわけではありません．

ぼくの中でのオブジェクト指向に基づき，Stateパターンを考えたらこうなった，というのが趣旨となります．
ですので，オブジェクト指向の考え方を参考にしていただければなと思います．

## Twitterフォローしてください

普段こういう系のツイートしてるので，興味のある方はフォローお願いします．
誰も絡んでくれなくて悲しいです．

https://twitter.com/Gorayan_kun/status/1531297481010860032?s=20&t=k-3FFI9HBl2CVa2BvG7Ijg

# 参考
https://ja.wikipedia.org/wiki/State_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3#:~:text=State%20%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%EF%BC%88%E8%8B%B1%3A%20state%20pattern,%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%81%AA%E6%89%8B%E6%AE%B5%E3%81%A8%E3%81%AA%E3%82%8B%E3%80%82

https://qiita.com/i-tanaka730/items/49ee4e3daa3aeaf6e0b5