Kotlinの委任されたプロパティ
1. 序章
Kotlinプログラミング言語は、クラスプロパティをネイティブでサポートしています。
プロパティは通常、対応するフィールドによって直接サポートされますが、必ずしもこのようである必要はありません。外部に正しく公開されている限り、プロパティと見なすことができます。
これは、ゲッターとセッターでこれを処理するか、デリゲートのパワーを活用することで実現できます。
2. 委任されたプロパティとは何ですか?
簡単に言えば、委任されたプロパティはクラスフィールドに支えられておらず、別のコードの取得と設定を委任します。これにより、委任された機能を抽象化して、複数の類似したプロパティ間で共有できます。 個別のフィールドではなく、マップにプロパティ値を格納します。
委任されたプロパティは、プロパティとそれが使用するデリゲートを宣言することによって使用されます。
例えば:
class DelegateExample(map: MutableMap<String, Any?>) {
var name: String by map
}
これは、 MutableMap 自体がデリゲートであるという事実を利用しており、そのキーをプロパティとして扱うことができます。
3. 標準の委任プロパティ
Kotlin標準ライブラリには、すぐに使用できる一連の標準デリゲートが付属しています。
MutableMapを使用して可変プロパティをバックアップする例はすでに見てきました。 同様に、 Map を使用して不変のプロパティをバックアップできます。これにより、個々のフィールドにプロパティとしてアクセスできますが、変更することはできません。
レイジーデリゲートを使用すると、プロパティの値を最初のアクセス時にのみ計算してからキャッシュすることができます。これは、計算にコストがかかる可能性があり、必要がない可能性があるプロパティに役立ちます。データベースからロード:
class DatabaseBackedUser(userId: String) {
val name: String by lazy {
queryForValue("SELECT name FROM users WHERE userId = :userId", mapOf("userId" to userId)
}
}
監視可能なデリゲートにより、プロパティの値が変更されるたびにラムダをトリガーできます。たとえば、変更通知や他の関連プロパティの更新が可能になります。
class ObservedProperty {
var name: String by Delegates.observable("<not set>") {
prop, old, new -> println("Old value: $old, New value: $new")
}
}
Kotlin 1.4以降、別のプロパティに直接委任することもできます。たとえば、APIクラスのプロパティの名前を変更する場合、古いプロパティをそのままにして、単に新しいプロパティに委任することができます。 1:
class RenamedProperty {
var newName: String = ""
@Deprecated("Use newName instead")
var name: String by this::newName
}
ここでは、 name プロパティにアクセスするときはいつでも、代わりにnewNameプロパティを効果的に使用しています。
4. 代理人の作成
すでに存在するデリゲートを使用するのではなく、デリゲートを作成したい場合があります。 これは、ReadOnlyPropertyまたはReadWritePropertyの2つのインターフェイスのいずれかを拡張するクラスの記述に依存しています。
これらのインターフェースは両方とも、 getValue と呼ばれるメソッドを定義します。これは、委任されたプロパティの現在の値を読み取るときに提供するために使用されます。 これは2つの引数を取り、プロパティの値を返します。
- thisRef –プロパティが含まれるクラスへの参照
- property –委任されているプロパティのリフレクションの説明
ReadWriteProperty インターフェイスは、プロパティの書き込み時にプロパティの現在の値を更新するために使用されるsetValueというメソッドを追加で定義します。 これは3つの引数を取り、戻り値はありません。
- thisRef –プロパティが含まれるクラスへの参照
- property –委任されているプロパティのリフレクションの説明
- value –プロパティの新しい値
Kotlin 1.4の時点で、 ReadWriteProperty インターフェイスは実際に拡張されます
例として、ローカルフィールドではなくデータベース接続に関して常に機能するデリゲートを作成しましょう。
class DatabaseDelegate<in R, T>(readQuery: String, writeQuery: String, id: Any) : ReadWriteDelegate<R, T> {
fun getValue(thisRef: R, property: KProperty<*>): T {
return queryForValue(readQuery, mapOf("id" to id))
}
fun setValue(thisRef: R, property: KProperty<*>, value: T) {
update(writeQuery, mapOf("id" to id, "value" to value))
}
}
これは、データベースにアクセスするための2つのトップレベル関数に依存します。
- queryForValue –これはいくつかのSQLといくつかのバインドを取り、最初の値を返します
- update –これはいくつかのSQLといくつかのバインドを取り、それをUPDATEステートメントとして扱います
次に、これを通常のデリゲートと同じように使用して、クラスをデータベースに自動的にバックアップさせることができます。
class DatabaseUser(userId: String) {
var name: String by DatabaseDelegate(
"SELECT name FROM users WHERE userId = :id",
"UPDATE users SET name = :value WHERE userId = :id",
userId)
var email: String by DatabaseDelegate(
"SELECT email FROM users WHERE userId = :id",
"UPDATE users SET email = :value WHERE userId = :id",
userId)
}
5. デリゲート作成の委任
Kotlin 1.4のもう1つの新機能は、デリゲートクラスの作成を別のクラスに委任する機能です。これは、PropertyDelegateProviderインターフェイスを実装することで機能します。実際のデリゲートとして使用するものをインスタンス化します。
これを使用して、使用するデリゲートの作成に関するコードを実行できます。たとえば、何が起こっているかをログに記録できます。 また、これを使用して、使用するプロパティに基づいて、使用するデリゲートを動的に選択することもできます。 たとえば、プロパティがnull許容の場合、別のデリゲートがある可能性があります。
class DatabaseDelegateProvider<code class="language-scala"><in R, T>(readQuery: String, writeQuery: String, id: Any)
: PropertyDelegateProvider<R, ReadWriteDelegate<R, T>> {
override operator fun provideDelegate(thisRef: T, prop: KProperty<*>): ReadWriteDelegate<R, T> {
if (prop.returnType.isMarkedNullable) {
return NullableDatabaseDelegate(readQuery, writeQuery, id)
} else {
return NonNullDatabaseDelegate(readQuery, writeQuery, id)
}
}
}
これにより、よりターゲットを絞ったケースに焦点を当てるだけでよいため、各デリゲートでより単純なコードを記述できます。 上記では、 NonNullDatabaseDelegate は、 null 値を持つことができないプロパティでのみ使用されることがわかっているため、それを処理するための追加のロジックは必要ありません。
6. 概要
プロパティの委任は強力な手法であり、他のプロパティの制御を引き継ぐコードを記述できるため、このロジックをさまざまなクラス間で簡単に共有できます。 これにより、通常のプロパティアクセスのように見える、堅牢で再利用可能なロジックが可能になります。
この記事の完全に機能する例は、GitHubのにあります。