🛡️

Kotlinのシールドクラスを使った例

2023/09/09に公開

Overview

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

シールされたクラスとインターフェイスは、継承をより制御しやすくする制限されたクラス階層を表します。シールされたクラスの直接のサブクラスはコンパイル時にすべてわかります。シールドされたクラスが定義されているモジュールやパッケージの外には、他のサブクラスを出現させることはできません。例えば、サードパーティのクライアントは、そのコードの中であなたのシールドされたクラスを拡張することはできません。このように、シールされたクラスの各インスタンスは、このクラスがコンパイルされた時点で既知の、限られたセットの中から型を持ちます。

同じことがシールされたインターフェースとその実装にも当てはまります。一旦シールされたインターフェースを持つモジュールがコンパイルされると、新しい実装が現れることはありません。

ある意味では、sealedクラスはenumクラスに似ています。enum型の値の集合も制限されていますが、各enum定数は単一のインスタンスとしてのみ存在するのに対し、sealedクラスのサブクラスは複数のインスタンスを持つことができ、それぞれが独自の状態を持ちます。

例として、ライブラリのAPIを考えてみよう。このAPIには、ライブラリのユーザーが投げられるエラーを処理するためのエラー・クラスが含まれている可能性が高い。そのようなエラークラスの階層に、パブリックAPIで見えるインターフェースや抽象クラスが含まれている場合、クライアントコードでそれらを実装したり拡張したりすることを妨げるものは何もありません。しかし、ライブラリは外部で宣言されたエラーについては知らないので、自分自身のクラスと一貫して扱うことはできません。エラークラスが密封された階層構造を持つことで、ライブラリの作者は、起こりうるすべてのエラータイプを把握し、それ以外のエラータイプが後から現れることがないようにすることができます。

密封されたクラスやインターフェイスを宣言するには、その名前の前にsealed修飾子をつけます:

sealed interface Error

sealed class IOError(): Error

class FileReadError(val file: File): IOError()
class DatabaseError(val source: DataSource): IOError()

object RuntimeError : Error

summary

前回書いた記事を書き換えて使っております。
https://zenn.dev/joo_hashi/articles/d48e7ef51ee53f

Jetpack Composeで使った例だと、前回紹介した画面遷移のロジックで使うようです。
'First Screen'と指定して書くとタイポする恐れがあるので、シールドクラスを定義して、パラメーターを渡した方が安全に画面遷移のルートを指定できる。

右クリックでシールドクラスを作成しましょう。ファイル名はDestinationsとしましょう。

package com.example.mynavigation

// このシールドクラスは、画面遷移のためのパラメーターを定義する
sealed class Destinations(val route: String) {
    // FirstScreenへ画面遷移するためのパラメーター
    object FirstScreen : Destinations("First Screen")
    // SecondScreenへ画面遷移するためのパラメーター
    object SecondScreen : Destinations("Second Screen")
}

全体のコード
前回書いた記事にコード追加しただけです。こちら文字が追加されてるので、画面遷移の違いがわかりやすくなっております。

package com.example.mynavigation

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.mynavigation.ui.theme.MyNavigationTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyNavigationTheme {
                // A surface container using the 'background' color from the theme
                Surface(
//                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    DisplayNav()
                }
            }
        }
    }
}

// どこのページへ移動するかnavControllerに定義する
@Composable
fun DisplayNav() {
    // navControllerを作成
    val navController = rememberNavController()
    // NavHostを作成
    // startDestinationは最初に表示するページ
    // startDestinationは最初に表示するページ
    NavHost(navController = navController,
        startDestination = Destinations.FirstScreen.toString()) {
        // 最初に表示するページ
        composable(route = Destinations.FirstScreen.toString()) {
            FirstScreen(navController = navController)
        }
        // 2番目に表示するページ
        composable(route = Destinations.SecondScreen.toString()) {
            SecondScreen(navController = navController)
        }
    }
}

// ボタンを押すと、画面遷移できるようにパラメーターを追加
@Composable
fun FirstScreen(navController: NavController) {
    Column {
        Text(text = "Welcome to the First Screen", fontSize = 64.sp, color = Color.Red, lineHeight = 64.sp)
        Button(onClick = { navController.navigate(Destinations.SecondScreen.toString()) }) {
            Text(text = "Go to SecondScreen")
        }
    }
}

@Composable
fun SecondScreen(navController: NavController) {
    Button(onClick = { navController.navigate(Destinations.FirstScreen.toString())}) {
        Text(text = "Go to 1st Screen")
    }
}

最初のページ

次のページ

thoughts

KotlinのSealedクラスをやってみて、Dartのシールドクラスも同じように、enumみたいなもんなんだな〜と使い道が理解できてきました。
多分定数を作って、パラメーターとして渡していただけかな。
https://dart.dev/language/class-modifiers

Jboy王国メディア

Discussion