Sealed Classについて
Sealed Classとは継承を制限して,、階層を明確にできるクラスです,。
詳しくは公式ドキュメントを參照してください,。
Sealed Classes - Kotlin Programming Language
もう少し分かりやすく言うと,、1つのファイル內でだけ継承できるクラスです,。
Sealed Classを使う利點
普通の(final or open )クラスと比べて
外部からの継承を制限できるので、コンパイル時に子クラスの階層が全て明らかになります,。
これで何ができるかというと,、when式とis演算子の組み合わせで網(wǎng)羅性のチェックができます。
例えば下のコードのように、通常の A を継承する B/C/D というクラスがある時に
open class A
class B : A()
class C : A()
class D : A()
このAの子クラスのインスタンスがどのクラスか判定して何か値を返す関數(shù)は
fun isSomething(obj : A) : Boolean {
return when (obj) {
is B -> true
is C -> true
is D -> false
}
}
と書くとコンパイルエラーになる(B/C/D 以外の可能性がないことを判別できない)ので,、以下のように書くと思います,。
fun isSomething(obj : A) : Boolean {
return when (obj) {
is B -> true
is C -> true
is D -> false
else -> throw RuntimeException("Not reached!!!!")
}
}
elseは(自分達が書いているコード的には)絶対通らないはずだけど、例外を投げるか何らかの値を返す必要があります,。
もちろんこのコードがライブラリーにあったとして,、利用者がA を継承したE というクラスを作って渡すと、このコードはelseに到達しますね,。
Sealed Classにすることで,、コンパイラーがこのA の子クラスはB/C/D しかないことを知られるので、最初のコードでも問題なくなります,。
こうすることで,、後で自分でA を継承する別のクラスを作ると、ここに選択肢を追加しないとコンパイルが通らなくなり,、処理の追加漏れがなくなります,。
前の項で書いている內容は、Enumでも実現(xiàn)できますね,。
Enumと比べてSealed Classを利用する利點は,、「Sealed Classを継承するのはobjectでもclassでもよい」ということではないでしょうか。
Sealed Classの子クラスそれぞれをEnumの値のように扱うことで,、「Enumの各値に可変の値を持たせるような仕組み」ができます,。
SwitftだとAssociated Valueと呼ばれるものですね。
使い方
使い方は簡単で,、class宣言の前にsealed と書き,、子クラスを全て同じファイル內で宣言するだけです。
sealed class A
class B : A()
class C : A()
class D : A()
object Z : A()
こうすることで,、クラスA はこのファイル內でしか継承できなくなります,。
また、A はabstractなクラスかつ,、コンストラクターがprivateになるので,、直接インスタンス生成はできなくなります。
あとは通常のクラスと同じように使います,。
どういう使い方をしている,?
私が実際にSealed Classを使うシチュエーションを紹介します。
決まった文字列(全てが定數(shù)ではない)を受け取りたい時
これはSealed Classの「ファイル外で継承できない」仕組みを活用する例です,。
例えばAndroidアプリでGoogle Analyticsにスクリーン名を送りたい時,、スクリーンは定められた値なので、大半が定數(shù)で定義可能です,。
しかし,、完全な定數(shù)で済めばいいのですが,、ページングをしている時にパラメーターとしてページ數(shù)を送りたいかもしれません。
また,、普通にString を送る運用だとミスって意味の分からない文字列を送ってしまうかもしれません,。
こういった場合にそれぞれのスクリーン用にSealed Classを継承したobjectやclassを作り、Google Analyticsの送信用のラッパーを用意して,、そこで値を分解して送るとこの問題を防ぐことができます,。
sealed class GAScreen() {
abstract val screenName: String
open class Simple(override val screenName: String) : GAScreen()
object Top : Simple("top")
object Settings : Simple("settings")
object List(private val page: Int) : GAScreen() {
override val screenName: String = "list[page=$page]"
}
}
ラッパー側は以下のようになります。
class GoogleAnalyticsWrapper() {
fun sendScreen(screen: GAScreen) {
val screenName = screen.screenName
// ここでスクリーン名を送る
}
}
こうすることで,、うっかり変な文字列を送ることがなくなります,。
色々な値を受け取って表示するActivityを起動するIntentに渡すパラメーターオブジェクト
こちらは"すごい
Intentに複數(shù)のパラメーターをセットしたい時,、全部を別々に手で受け取るのは面倒ですよね。
そのパラメーターの中にNullableなものがあって,、別のパラメーターの値によって値が入ってなかったりすると思います,。
こういう時にパラメーターオブジェクトを作って、Parcelableにして渡すのが有効と考えられます,。
sealed class MainScreen() {
object Top : MainScreen()
object Settings : MainScreen()
class Feed(val skipTutorial: Boolean) : MainScreen()
class Category(val category: Category) : MainScreen()
class EntryList(val initialTab: Tab) : MainScreen()
}
これをActivity側で受け取ってwhen式を使うなどして処理してやります,。
when(screen) {
is Top -> {
openTop()
}
is Settings -> {
openSettings()
}
is Feed -> {
openFeed(screen.skipTutorial)
}
is Category -> {
openCategory(screen.category)
}
is EntryList -> {
openEntryList(screen.initialTab)
}
}
最後に
KotlinのSealed Classを使って堅牢な設計のアプリを作りましょう。
|