初めてのkotlinを触る - Sealed Classes(1)

# 目的
* enum classを紹介した
 * https://qiita.com/alingogo/items/5200b0862e8a1edfcce9
 * 特にjavaと違う使い方を説明した
* 本文はkotlinに特別なクラス(Sealed Classes)を紹介する

# 概要
* Sealed Classesの基本
* Sealed Classesでの状態管理

# 内容
## Sealed Classesの基礎
#### 目的

https://kotlinlang.org/docs/reference/sealed-classes.html

```
They are, in a sense, an extension of enum classes: the set of values for an enum
type is also restricted, but each enum constant exists only as a single instance,
whereas a subclass of a sealed class can have multiple instances which can contain state.
```

* Sealed Classesは、Enum Classesの拡張になる
* Enum Classesがの値の集合で制限されていますが、各列挙型定数は単一のインスタンスとして存在する
* Sealed Classesのサブクラスは、複数インスタンスであり、特定な状態を持っている

```
A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.
```

* Sealed Classesは抽象クラスであり、直接的にインスタンス化できなくて、抽象メンバーを持っている
* Sealed Classesには、valueを含むことができる 


```
To declare a sealed class, you put the sealed modifier before the name of the class. A sealed class can have subclasses, but all of them must be declared in the same file as the sealed class itself.
Note that classes which extend subclasses of a sealed class (indirect inheritors) can be placed anywhere, not necessarily in the same file.
```

* Sealed Classesを宣言するには、クラスの名前の前に修飾子(sealed)を付ける。
* Sealed Classesはサブクラスを持つことができるが、それらのすべては継承されるクラス自体と同じファイルで宣言されなければならない
* Sealed Classesの継承したサブクラスを拡張するクラス（間接継承）は、同じファイル内ではなく、どこにでも配置できる

#### 定義と利用

方法1

```
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}

fun main() {
    println(eval(Sum(Const(12.3), Const(12.4)))) // 24.700000000000003
}
```

方法2

```
sealed class Expr {
  data class Const(val number: Double) : Expr()
  data class Sum(val e1: Expr, val e2: Expr) : Expr()
  object NotANumber : Expr()
}

fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
    Expr.NotANumber -> Double.NaN
}

fun main() {
    println(eval(Expr.Sum(Expr.Const(12.3), Expr.Const(12.4))))
}
```

* 表現できるものの範囲が広がる
* enum定数に宣言後に値を持たせることはできない



#### メリット

* 継承のことを制限できる
* サブクラスの範囲を固定できる

```
fun eval(expr: Expr): Double = when(expr) {
    is Expr.Const -> expr.number
    is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
}
```

* whenに全てのケースを含んでいない場合は、エラーが出力される

```
'when' expression must be exhaustive, add necessary 'NotANumber' branch or 'else' branch instead
```


## Sealed Classesでの状態管理


```


enum class State {
    IDLE, PAUSED, PLAYING
}

sealed class PlayerCmd {
    class Play(val url: String, val position: Long = 0): PlayerCmd()
    class Seek(val position: Long): PlayerCmd()
    object Pause: PlayerCmd()
    object Resume: PlayerCmd()
    object Stop: PlayerCmd()
}

class Player {
    private var state: State = State.IDLE
    
    private fun sendCmd(cmd: PlayerCmd) {
        when (cmd) {
            is PlayerCmd.Play -> {
                println("\nPlay ${cmd.url} from ${cmd.position}ms")
                state = State.PLAYING
                doPlay(cmd.url, cmd.position)
            }
            is PlayerCmd.Resume -> {
                println("\nResume. ")
                state = State.PLAYING
                doResume()
            }
            is PlayerCmd.Pause -> {
                println("\nPause. ")
                state = State.PAUSED
                doPause()
            }
            is PlayerCmd.Stop -> {
                println("\nStop.")
                state = State.IDLE
                doStop()
            }
            is PlayerCmd.Seek -> {
                println("\nSeek to ${cmd.position}ms, state: $state")
            }
        }
    }

    private fun doPlay(url: String, position: Long) {
        //todo
    }

    private fun doResume(){
        //todo
    }

    private fun doPause() {
        //todo
    }

    private fun doStop() {
        //todo
    }

    fun play(url: String, position: Long = 0) {
        sendCmd(PlayerCmd.Play(url, position))
    }

    fun resume() {
        sendCmd(PlayerCmd.Resume)
    }

    fun pause() {
        sendCmd(PlayerCmd.Pause)
    }

    fun stop() {
        sendCmd(PlayerCmd.Stop)
    }

    fun seekTo(position: Long) {
        sendCmd(PlayerCmd.Seek(position))
    }
}

fun main() {
    val player: Player = Player()
    player.play("http://ws.stream.qqmusic.qq.com/C2000012Ppbd3hjGOK.m4a") // Play http://ws.stream.qqmusic.qq.com/C2000012Ppbd3hjGOK.m4a from 0ms
    player.pause() // Pause.
    player.resume() // Resume.
    player.seekTo(30000) // Seek to 30000ms, state: PLAYING
    player.stop() // Stop.
}
```

* [コード](https://play.kotlinlang.org/#eyJ2ZXJzaW9uIjoiMS4zLjQxIiwicGxhdGZvcm0iOiJqYXZhIiwiYXJncyI6IiIsImpzQ29kZSI6IiIsIm5vbmVNYXJrZXJzIjp0cnVlLCJ0aGVtZSI6ImlkZWEiLCJjb2RlIjoiLyoqXG4gKiBZb3UgY2FuIGVkaXQsIHJ1biwgYW5kIHNoYXJlIHRoaXMgY29kZS4gXG4gKiBwbGF5LmtvdGxpbmxhbmcub3JnIFxuICovXG5cbmVudW0gY2xhc3MgU3RhdGUge1xuICAgIElETEUsIFBBVVNFRCwgUExBWUlOR1xufVxuXG5zZWFsZWQgY2xhc3MgUGxheWVyQ21kIHtcbiAgICBjbGFzcyBQbGF5KHZhbCB1cmw6IFN0cmluZywgdmFsIHBvc2l0aW9uOiBMb25nID0gMCk6IFBsYXllckNtZCgpXG4gICAgY2xhc3MgU2Vlayh2YWwgcG9zaXRpb246IExvbmcpOiBQbGF5ZXJDbWQoKVxuICAgIG9iamVjdCBQYXVzZTogUGxheWVyQ21kKClcbiAgICBvYmplY3QgUmVzdW1lOiBQbGF5ZXJDbWQoKVxuICAgIG9iamVjdCBTdG9wOiBQbGF5ZXJDbWQoKVxufVxuXG5jbGFzcyBQbGF5ZXIge1xuICAgIHByaXZhdGUgdmFyIHN0YXRlOiBTdGF0ZSA9IFN0YXRlLklETEVcbiAgICBcbiAgICBwcml2YXRlIGZ1biBzZW5kQ21kKGNtZDogUGxheWVyQ21kKSB7XG4gICAgICAgIHdoZW4gKGNtZCkge1xuICAgICAgICAgICAgaXMgUGxheWVyQ21kLlBsYXkgLT4ge1xuICAgICAgICAgICAgICAgIHByaW50bG4oXCJcXG5QbGF5ICR7Y21kLnVybH0gZnJvbSAke2NtZC5wb3NpdGlvbn1tc1wiKVxuICAgICAgICAgICAgICAgIHN0YXRlID0gU3RhdGUuUExBWUlOR1xuICAgICAgICAgICAgICAgIGRvUGxheShjbWQudXJsLCBjbWQucG9zaXRpb24pXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpcyBQbGF5ZXJDbWQuUmVzdW1lIC0+IHtcbiAgICAgICAgICAgICAgICBwcmludGxuKFwiXFxuUmVzdW1lLiBcIilcbiAgICAgICAgICAgICAgICBzdGF0ZSA9IFN0YXRlLlBMQVlJTkdcbiAgICAgICAgICAgICAgICBkb1Jlc3VtZSgpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpcyBQbGF5ZXJDbWQuUGF1c2UgLT4ge1xuICAgICAgICAgICAgICAgIHByaW50bG4oXCJcXG5QYXVzZS4gXCIpXG4gICAgICAgICAgICAgICAgc3RhdGUgPSBTdGF0ZS5QQVVTRURcbiAgICAgICAgICAgICAgICBkb1BhdXNlKClcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlzIFBsYXllckNtZC5TdG9wIC0+IHtcbiAgICAgICAgICAgICAgICBwcmludGxuKFwiXFxuU3RvcC5cIilcbiAgICAgICAgICAgICAgICBzdGF0ZSA9IFN0YXRlLklETEVcbiAgICAgICAgICAgICAgICBkb1N0b3AoKVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaXMgUGxheWVyQ21kLlNlZWsgLT4ge1xuICAgICAgICAgICAgICAgIHByaW50bG4oXCJcXG5TZWVrIHRvICR7Y21kLnBvc2l0aW9ufW1zLCBzdGF0ZTogJHN0YXRlXCIpXG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwcml2YXRlIGZ1biBkb1BsYXkodXJsOiBTdHJpbmcsIHBvc2l0aW9uOiBMb25nKSB7XG4gICAgICAgIC8vdG9kb1xuICAgIH1cblxuICAgIHByaXZhdGUgZnVuIGRvUmVzdW1lKCl7XG4gICAgICAgIC8vdG9kb1xuICAgIH1cblxuICAgIHByaXZhdGUgZnVuIGRvUGF1c2UoKSB7XG4gICAgICAgIC8vdG9kb1xuICAgIH1cblxuICAgIHByaXZhdGUgZnVuIGRvU3RvcCgpIHtcbiAgICAgICAgLy90b2RvXG4gICAgfVxuXG4gICAgZnVuIHBsYXkodXJsOiBTdHJpbmcsIHBvc2l0aW9uOiBMb25nID0gMCkge1xuICAgICAgICBzZW5kQ21kKFBsYXllckNtZC5QbGF5KHVybCwgcG9zaXRpb24pKVxuICAgIH1cblxuICAgIGZ1biByZXN1bWUoKSB7XG4gICAgICAgIHNlbmRDbWQoUGxheWVyQ21kLlJlc3VtZSlcbiAgICB9XG5cbiAgICBmdW4gcGF1c2UoKSB7XG4gICAgICAgIHNlbmRDbWQoUGxheWVyQ21kLlBhdXNlKVxuICAgIH1cblxuICAgIGZ1biBzdG9wKCkge1xuICAgICAgICBzZW5kQ21kKFBsYXllckNtZC5TdG9wKVxuICAgIH1cblxuICAgIGZ1biBzZWVrVG8ocG9zaXRpb246IExvbmcpIHtcbiAgICAgICAgc2VuZENtZChQbGF5ZXJDbWQuU2Vlayhwb3NpdGlvbikpXG4gICAgfVxufVxuXG5mdW4gbWFpbigpIHtcbiAgICB2YWwgcGxheWVyOiBQbGF5ZXIgPSBQbGF5ZXIoKVxuICAgIHBsYXllci5wbGF5KFwiaHR0cDovL3dzLnN0cmVhbS5xcW11c2ljLnFxLmNvbS9DMjAwMDAxMlBwYmQzaGpHT0subTRhXCIpIC8vIFBsYXkgaHR0cDovL3dzLnN0cmVhbS5xcW11c2ljLnFxLmNvbS9DMjAwMDAxMlBwYmQzaGpHT0subTRhIGZyb20gMG1zXG4gICAgcGxheWVyLnBhdXNlKCkgLy8gUGF1c2UuXG4gICAgcGxheWVyLnJlc3VtZSgpIC8vIFJlc3VtZS5cbiAgICBwbGF5ZXIuc2Vla1RvKDMwMDAwKSAvLyBTZWVrIHRvIDMwMDAwbXMsIHN0YXRlOiBQTEFZSU5HXG4gICAgcGxheWVyLnN0b3AoKSAvLyBTdG9wLlxufSJ9)


#### javaで実現
* https://www58.atwiki.jp/chapati4it/pages/103.html


#### まとめ
* kotlinのSealed Classが簡単ではないか
* 次はSealed Classで例外処理を紹介する