Kotlinのシールドクラスを使った例
Overview
シールされたクラスとインターフェイスは、継承をより制御しやすくする制限されたクラス階層を表します。シールされたクラスの直接のサブクラスはコンパイル時にすべてわかります。シールドされたクラスが定義されているモジュールやパッケージの外には、他のサブクラスを出現させることはできません。例えば、サードパーティのクライアントは、そのコードの中であなたのシールドされたクラスを拡張することはできません。このように、シールされたクラスの各インスタンスは、このクラスがコンパイルされた時点で既知の、限られたセットの中から型を持ちます。
同じことがシールされたインターフェースとその実装にも当てはまります。一旦シールされたインターフェースを持つモジュールがコンパイルされると、新しい実装が現れることはありません。
ある意味では、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
前回書いた記事を書き換えて使っております。
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みたいなもんなんだな〜と使い道が理解できてきました。
多分定数を作って、パラメーターとして渡していただけかな。
Discussion