2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlin の sealed クラスを使う

Posted at

概要

Kotlin の sealed クラスの使い方を、実際に使ってみて考えたので書いてみます。

sealed とは

同じクラス内か同じ .kt ファイル内のクラスのみ継承を許可するキーワードです。

sealed という英単語は seal の過去形または過去分詞形で、「封印された」という意味のようです。某ポータルサイトで画像検索するとアザラシの画像ばかり出てきます。


実装

以下の状況でこの sealed が役立ちそうだと思いました。

  • 同一の振る舞いをして、属性が異なるようなクラス群を定義したい
  • 振る舞いの実装は外から書き換えられないようにしたい

例として、Android のアプリ内保存領域のラッパーを作る際に役立つのではないかと考えました。Android アプリでは CacheDir と FilesDir の2種類をアプリ内のデータ保存領域として用いることができます。その2種類の領域では基本的に同じようにファイルを扱うことができます。

領域 違い
CacheDir アプリケーション管理でキャッシュを削除すると保存したファイルが消える
FilesDir アプリを削除すると保存したファイルが消える

そのため、実装は共通させて、保存場所だけを初期化時に固定値で分ける、というやり方でクラスを定義したいと考え、こういう時に sealed がはまるのではないかと思いました。

共通の振る舞いを実装

File オブジェクトの割り当て、取得、インデックス指定での削除、全削除の関数を定義します。

open class StorageWrapper(context: Context, dirName: String) {

    private val dir: File

    fun assignNewFile(name: String): File {
        val matcher = ILLEGAL_FILE_NAME_CHARACTER.matcher(name)
        if (matcher.find()) {
            return File(dir, matcher.replaceAll("_"))
        }
        return File(dir, name)
    }

    fun get(index: Int): File? {
        if (index < 0 || index > listFiles().size) {
            return null
        }
        return listFiles()[index]
    }

    fun removeAt(index: Int): Boolean {
        if (index < 0 || index > listFiles().size) {
            return false
        }
        return listFiles()[index].delete()
    }

    fun clean() = dir.listFiles().forEach { it.delete() }

}

準備

sealed キーワードを付けてコンストラクタを追加します。

sealed class StorageWrapper(context: Context, dirName: String) {

    init {
        dir = File(getDir(context), dirName)
        if (!dir.exists()) {
            dir.mkdirs()
        }
    }

    protected abstract fun getDir(context: Context): File

サブクラスを定義

sealed クラスのサブクラスを定義する場合、同じ .kt ファイルの、sealed クラスの外側か内側かに定義します。

同一 .kt ファイル内の場合

まず外側に定義するやり方です。通常通りサブクラスを定義すれば OK です。

外側
sealed class StorageWrapper(context: Context, dirName: String) {

    private val dir: File

    protected abstract fun getDir(context: Context): File

    // ...
}

class CacheDir(context: Context, dirName: String) : StorageWrapper(context, dirName) {

    override fun getDir(context: Context): File = context.cacheDir
}

class FilesDir(context: Context, dirName: String) : StorageWrapper(context, dirName) {

    override fun getDir(context: Context): File = context.filesDir
}

かつてはこの方法はサポートされていなかったらしいですが、現在はOKのようです。こちらですと、下記のように CacheDir や FilesDir クラスを直接指定してインスタンスにすることが可能です。

インスタンス化
val cacheDir = CacheDir(context, "dir_name")
val filesDir = FilesDir(context, "dir_name")

同一クラス内の場合

次に、内側に定義するやり方です。sealed クラスの内部にサブクラスを定義します。

内側
sealed class StorageWrapper(context: Context, dirName: String) {

    private val dir: File

    protected abstract fun getDir(context: Context): File

    // ...

    class CacheDir(context: Context, dirName: String) : StorageWrapper(context, dirName) {

        override fun getDir(context: Context): File = context.cacheDir
    }

    class FilesDir(context: Context, dirName: String) : StorageWrapper(context, dirName) {

        override fun getDir(context: Context): File = context.filesDir
    }
}

このケースではインスタンスを生成する際、下記のように

インスタンス化
StorageWrapper.CacheDir(context, "dir_name")
StorageWrapper.FilesDir(context, "dir_name")

とする必要があります。


参考

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?