Kotlinでストラテジーパターン

## はじめに
[良いコード／悪いコードで学ぶ設計入門](https://gihyo.jp/book/2022/978-4-297-12783-1)を読んでいて、「ストラテジーパターンってこんなに便利なのか！」と驚いたのでまとめてみます。

## 0. ストラテジーパターンとは
すごい大雑把に言うと、ifやwhenなどを使わず、interfaceを用いることで、条件分枝処理を実装するような設計です。詳しい説明やクラス図についてはTECHSCOREさんやIT専科さんに書いてありますので、そちらを見てくださいね。

https://www.techscore.com/tech/DesignPattern/Strategy

http://www.itsenka.com/contents/development/designpattern/strategy.html

今回は、映画館で料金（大人:1800, 子供:1000, ペア:2500）とプレゼント（大人:プレゼントなし, 子供:ぬいぐるみ, ペア:花束）を返却するような処理を考えてみます。



## 1. ストラテジーパターンなしで実装するとどうなるか
ストラテジーパターンなしでの実装例です。
```kotlin:Cinema.kt
class Cinema(val type: Type){
    
    fun price(): Int {
        val price = 
            when(type){
                Type.ADULT -> 1800
                Type.KID -> 1000
                Type.PAIR -> 2500
            }
        return price
    }
    
    fun present() {
        when(type){
            Type.ADULT -> println("なし")
            Type.KID -> println("ぬいぐるみ")
            Type.PAIR -> println("花束")
        }
    }
}
```

```kotlin:Type.kt
enum class Type{
    ADULT,
    KID,
    PAIR
}
```

```kotlin:main.kt
fun main() {
    val cinema = Cinema(Type.PAIR)
    val price = (cinema.price())
    println(price)
    cinema.present()   
}
```
この程度の処理でしたら、正直なところストラテジーパターンを使う必要はないかもしれません。
しかし、属性（type）が"シニア", "大人", "大学生", "小中高生", "幼稚園以下", "ペア", "特別パスポート会員" and more...と増え、メソッドもprice(), present(), numberOfSeat() and more...と増えると話は別です。あっという間に下のような~~クソ~~コードが出来上がるでしょう。 
```kotlin:Cinema.kt
class Cinema(val type: Type){
    
    fun price(): Int {
        val price = 
            when(type){
                Type.SENIOR -> 1500
                Type.ADULT -> 1800
                Type.UNIVERSITY_STUDENT -> 1000
                Type.KID -> 500
                Type.BABY -> 0
                Type.PAIR -> 2500
                Type.SPECIAL -> 1000
        }
        return price
    }
    
    fun present() {
        when(type){
                Type.SENIOR -> println("なし")
                Type.ADULT -> println("なし")
                Type.UNIVERSITY_STUDENT -> println("うまい棒")        
                Type.KID -> println("ぬいぐるみ")
                Type.BABY -> println("ぬいぐるみ")
                Type.PAIR -> println("花束")
                Type.SPECIAL -> println("記念写真")
        }
    }

　　　　　　　　fun numberOfSeat(): Int {
        val numberOfSeat = 
            when(type){
                Type.SENIOR -> 1
                Type.ADULT -> 1
                Type.UNIVERSITY_STUDENT -> 1
                Type.KID -> 1
                Type.BABY -> 0
                Type.PAIR -> 2
                Type.SPECIAL -> 1
        }
        return numberOfSeat
    }
}
```
これでは
①コードの重複が多数あるため、属性が追加された時に実装漏れが起こりやすい
②メソッドが追加されるたびに、重複コードが生じる
など保守性に乏しいコードになってしまいます。ちなみに上のコードだと、presentメソッドでType.SPECIALを実装し忘れたとしてもコンパイルは通ってしまいます。あな恐ろしや恐ろしや。

そこでストラテジーパターンの登場です。



## 2. ストラテジーパターンを用いて実装するとどうなるか
ストラテジーパターンでは、アルゴリズムをStrategyインターフェイスに記述し、これを属性毎の具体的なStrategyクラスで実装します。


```kotlin:Strategy.kt
interface Strategy{    
    fun price(): Int
    fun present()
}
```

```kotlin:AdultStrategy.kt
class AdultStrategy: Strategy{
    override fun price(): Int {
        return 1800
    }
    
    override fun present(){
        println("なし")
    }
}
```

```kotlin:KidStrategy.kt
class KidStrategy: Strategy{
    override fun price(): Int {
        return 1000
    }
    
    override fun present(){
        println("ぬいぐるみ")
    }
}
```

```kotlin:PairStrategy.kt
class PairStrategy: Strategy{
    override fun price(): Int {
        return 2500
    }
    
    override fun present(){
        println("花束")
    }
}
```
その後、Strategyを切り替え役をContextクラスで行います。

```kotlin: Context.kt
class Context(val strategy: Strategy){
	fun price(): Int {
        return strategy.price()
    }
    
    fun present() {
        return strategy.present()
    }
}
```


```kotlin:main.kt
fun main() { 
    val cinema = Cinema(AdultStrategy())
    val price = (cinema.price())
    println(price)
    cinema.present()
}
```

上ではmain()でAdultStrategyをインスタンス化していますが、以下のようにmapを使えばenumを渡すだけで処理ができます。

```kotlin:main.kt
fun main() {
    val adultStrategy = AdultStrategy()
    val kidStrategy = KidStrategy()
    val pairStrategy = PairStrategy()
    
    val maps = hashMapOf<Type, Strategy>()
    maps.put(Type.ADULT, adultStrategy)
    maps.put(Type.KID, kidStrategy)
    maps.put(Type.PAIR, pairStrategy)
    
    val context = Context(maps.get(Type.ADULT) ?: adultStrategy)
    val price = (context.price())
    println(price)
    context.present()
}
```


Strategyパターンでは、アルゴリズムをStrategyインターフェイスに記述するということがミソです。
属性が増えた場合、具体的なStrategyクラスを作成してインターフェースを実装すれば良いので、機能追加が簡単になり、コードの修正漏れも大幅に減少します！



参考にさせていただきました
https://zenn.dev/nekoniki/articles/396ebc523930c196dc13

ここ間違っているよーなど、コメントありましたら、ぜひお願いします！