Sealed Classを全然読み解けなかった過去の自分へ

:::note warn
警告
登場人物は架空のものです。
:::

# Sealed Classがわからなかった
あれは2年前のことです。大学の私は、いつものようにAndroidアプリを開発していました。

ぼく「ねえルイーズ、そのSealed Calssっていうの、どうして使ってるの？」
```Result.kt
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}
```
ルイーズ「ああ、それはみんながそうやってるからだよ！」

〜完〜

sealed classによる状態管理は、意味がわかればコードの表現力を上げてくれると思うのですが、情報量的にも、どうにも初心者潰しな要因が多すぎると思いました。

# Sealed Class(Interface)とは
Sealedは継承を制限する修飾子。
定義されているモジュールおよびパッケージの外側で継承(実装)ができなくなる

# Enumとの違い
継承させずに状態を扱うならenumでいいんじゃないか？と思うかもしれません。enumも継承できません。
loading, success, errorを扱うだけならこう言うふうにenumで定義することもできます。

```Result.kt
enum class Result{
    Loading,
    Success,
    Error
}
```

しかしながらenumだけではプロパティの扱いが難しく、状態に応じてデータを持たせることができません。
そこでsealed classを使ったらどういうことができるようになるのか考えてみます。

# コードを読み解く
先ほどのルイーダが書いたコードをもう一度見てみましょう。
よくわからなかったポイントを1つずつ挙げていきます。
1. out T
1. object
1. Nothing

### out Tの使い方
```Result.kt
sealed class Result<out T> {
```

まず、Tについて。
これはGenerics(総称型)と呼ばれ、型をパラメータとすることを可能としたものになります。
一般的に<T>のように<>の部分がGenericsとなります。

インスタンス生成時に保存する型を定義できるので、こうすることで好きな型のデータを通信処理で取得するような使い方ができます。

RとかEとか使われることもありますが、単なる識別子なので処理には影響しません。←ここややこしい

どんな型でも使いたいならAnyでいいんじゃないの？って思うかもしれませんが、**Anyと違って型を明示的に示すことができます**。

```Container.kt
class Container(a: Any) {
    var value = a
}

fun main() {
    val container1 = Container(1234)
    val container2 = Container("abdc")

    // どんな型でも入ってしまう
    val list = arrayListOf<Container>(container1, container2)
}
```

```Container.kt
class Container<T>(t: T) {
    var value = t
}

fun main() {
    val container1 = Container(1234)
    val container2 = Container("abdc")

    // コンパイルエラー
    val list = arrayListOf<Container>(container1, container2)
}
```

次にoutについて
out修飾子を使うことで、値を返す時のみ使われることを宣言できます。なので、**後から値を変えることができないので型安全になります。**

getter/setterのうちsetterができない状態です。

### objectの使い方
```Result.kt
    object Loading : Result<Nothing>()
```
objectは単一インスタンスのみを持つクラス(シングルトン)を作るキーワードです。
**状態を持たない定数**を定義する時に使うので、enum定数とあまり変わりません。
ここでは、ローディング中であることを表現しているだけなので、データやエラーメッセージを持たず定数として扱うことができます。

### Nothingの使い方
```Result.kt
    object Loading : Result<Nothing>()
    data class Error(val exception: Exception) : Result<Nothing>()
```
Nothingは全てのクラスのサブクラス(全てのクラスを継承している)です。インスタンスを持ちません。
反対になるのがAnyクラスで、Anyクラスは全てのクラスのスーパークラス（全てのクラスの継承元）です。
使い方としてはいろいろあるのですが、ここでは型引数を省略できない場合に、**値を持たないことを明治的に示す**という役割を担っています。

# まとめ
改めてルイーズの書いたコードとenumで書いたものを比較してみようと思います。
```Result.kt
sealed class Result<out T> {
    object Loading : Result<Nothing>()
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}
```
```Result.kt
enum class Result{
    Loading,
    Success,
    Error
}
```

どうでしょうか？意味がわかると、**状態の管理が表現力豊かに描かれているように見えるのではないでしょうか？**(そもそも処理が同じコードではないが)
sealed classを使って状態管理をする必要があるとは限らないのですが、このようなコード自体は最近よく見かけると思ったので、この記事がコードリーディングの一助になれば幸いです。

# 参考
https://qiita.com/kikuchy/items/ad89a12029082be8d218

https://zenn.dev/96mame/articles/21e2f8c95947e9581192

https://takusan.negitoro.dev/posts/kotlin_class_generics_sealed/

https://qiita.com/reoc-software/items/fdc6a7f09621ecf9d202

https://phoneappli.hatenablog.com/entry/2021/03/30/180749