blog

Kotlin上級 - ロジックを再利用してプロパティにアクセスする:デリゲート・プロパティ

p プロパティはアクセッサロジックを別のオブジェクトに委譲します。このオブジェクトにアクセスするには、キーワード by を使った式を評価します。 規約により、Delegate クラスは getValu...

Apr 1, 2020 · 4 min. read
シェア

はじめに: 委譲とは、ある操作を行うオブジェクト自身がその操作を行う必要はなく、別の補助オブジェクトにその操作を委譲するデザインパターンで、その補助オブジェクトを**delegation**と呼びます。

I. 委任属性の基本操作

delegate属性の基本構文は以下のようになります:

class Foo {
 var p: Type by Delegate()
}

ここではDelegate クラスの新しいインスタンスです。このオブジェクトにアクセスするには、キーワード **by を使った式を評価します。

//  
class Foo {
 // コンパイラは自動的にヘルパー・プロパティ
 private val delegate = Delegate()
 
 //  ”マップへのアクセスはすべて、対応する "delegate "を呼び出すことで行われる。”のgetValueメソッドとsetValueメソッドは、マップの値として使用される。
 var P: Int
 set(value: Int) = delegate.setValue(..., value)
 get() = delegate.getValue()
}

慣習として、Delegate クラスは getValue メソッドと setValue メソッドを持たなければなりません。

class Delegate {
 // getValueこれは、ゲッター
 operator fungetValue(...) { ... }
 // setValueこれは、セッター
 operator fun setValue(..., value: Type)
}
class Foo {
 // キーワード "by”アトリビュートをデリゲート・オブジェクトに関連付ける。
 var p: Type by Delegate()
}
>>> val foo = Foo()
// プロパティの値は、delegateを呼び出すことでマップの値として使用される。.getValue(...)プロパティの変更を実装するには
>>> val oldValue = foo.p
// プロパティの値は、delegateを呼び出すことでマップの値として使用される。.setValue(..., newValue)属性の変更を実装するために
>>> foo.p = newValue

II.デリゲート・プロパティの使用: イナート初期化と "by lazy()"

不活性初期化は一般的なパターンで、特に初期化処理で多くのリソースを消費し、オブジェクトの使用時にデータが必ずしも必要でない場合に便利です。

  • 不活性初期化にsupport属性を使用
//  
class Email {/* ... */}
fun loadEmail(person: Person): List<Email> {
 println("Load emails for ${person.name}")
 return listOf(/*...*/)
}
class Person(val name: String) {
 // "emails”この属性は、デリゲートに関連するデータを格納するために使われる。
 private var _emails: List<Email>? = null
 val emails: List<Email>
 get() {
 if(_emails == null) {
 // アクセス時にファイルをロードする
 _emails = loadEmail(this)
 }
 // すでにロードされている場合は、単に
 return _emails!!
 }
}
//  
>>> val person = Person("Alice")
// 最初の訪問では
>>> person.emails
Load emails for Alice
>>> person.emails

上記のコードでは、_emailsという属性を使って値を保存し、別のemailsという属性を使ってその属性への読み取りアクセスを提供するという、サポート属性のテクニックを使っています。

コードを減らすために、Kotlinはラベル付きライブラリ関数***lazy***によって返されるデリゲートを提供します。

  • デリゲート属性による不活性初期化
class Person(val name: String) {
 val emails by lazy { loadEmails(this) }
}

lazy関数は、正しいシグネチャを持つgetValueというメソッドを持つオブジェクトを返します。

lazyの引数は、値を初期化するために呼び出されるラムダです。デフォルトでは、遅延関数のスタイルはスレッドセーフです。

マップへのプロパティ値の保存

カスタムオブジェクト: 委譲されたプロパティが活躍するもう一つの一般的な用途は、動的に定義されたプロパティセットを持つオブジェクトです。

  • 属性を定義し、値をマップに格納します。
//  
class Person {
 private val _attributes = hashMapOf<String, String>()
 fun setAttribute(attrName: String, value: String) {
 _attributes[attrName] = value
 }
 val name: String
 // マップからプロパティを手動で取得する
 get() = _attributes["name"]!!
}
//  
>>> val p = Person6()
>>> val data = mapOf("name" to "Dmitry", "company" to "JetBrains")
>>> for((attrName, value) in data)
 p.setAttribute(attrName, value)
>>> println(p.name)
Dmitry

_attributes.getValue(p, prop)_attributes[prop.name]上記のコードでは、p.nameが , の呼び出しを隠しています。

  • マップに値を格納するにはdelegate属性を使用します。
class Person {
 private val _attributes = hashMapOf<String, String>()
 fun setAttribute(attrName: String, value: String) {
 _attributes[attrName] = value
 }
 // マップをデリゲート・プロパティとして使う
 val name: String by _attributes
}

上記のコードでは、属性の名前が自動的にマップのキーとして使用され、属性の値がマップの値として使用されます。

Read next

OpenGL ES読み込みテクスチャ画像反転のためのいくつかの解決策

I. はじめに\n\n解答\n反転の原因がわかったので、それを解決しましょう。\n選択肢1 - 行列変換\nESでは画像を回転させたり、ずらしたりするために行列がよく使われます。

Apr 1, 2020 · 6 min read