委譲プロパティに関する変更点(2)
委譲プロパティはローカルプロパティでも可能
こういった委譲プロパティは、ローカルプロパティでも可能なようにバージョン1.1で変更されています。そのため、例えば先のConnectionFactory()は、リスト5のように関数内でも利用できます。
val connection: Connection by ConnectionFactory() connection.connect()
委譲先インターフェース
リスト3では、委譲先クラスとして、通常のクラスを作成してgetValue()演算子とsetValue()演算子をオーバーライドする方法を採りました。
一方、Kotlinの標準ライブラリには委譲先クラスを表すインターフェースとして、valプロパティの委譲先用のReadOnlyPropertyとvarプロパティの委譲先用のReadWritePropertyが用意されています。このインターフェースを実装する形で、getValue()メソッド(とsetValue()メソッド)をオーバーライドする方法も可能です。なお、getValue()メソッドとsetValue()メソッドのシグネチャは、表1と同じです。
この仕組みは、クラスではなく、無名オブジェクトを委譲先として用意する場合に有効です。例えば、リスト6のコードです。
val connectionFactory = object : ReadOnlyProperty<Any?, Connection> { override fun getValue(thisRef: Any?, property: KProperty<*>): Connection { : } } val connectionObj: Connection by connectionFactory connectionObj.connect()
リスト6での注意点は、ReadOnlyPropertyとReadWritePropertyを利用する場合は、そのジェネリクスとして委譲元プロパティが所属するクラスとプロパティそのもののデータ型(表1のOwnerとV)を指定する必要があることです。リスト6では、ローカルプロパティへの委譲のため、そもそも所属先クラスがわからないのでAny?としています。
なお、このReadWritePropertyインターフェースがReadOnlyPropertyインターフェースを継承するようにバージョン1.4で変更されています。そのため、ReadWritePropertyインターフェースを実装したクラス(オブジェクト)をvalプロパティの委譲先とできるようになっています(もちろん、その際、setValue()メソッドの実装コードは実行されずに無駄とはなってしまいますが)。
委譲先インスタンスの生成コードを記述する
委譲プロパティに関して最後に紹介するのは、バージョン1.1で導入されたprovideDelegate()演算子です。これは、委譲先インスタンスを生成できる演算子です。
リスト3のように、委譲先クラスを用意した場合、byの次にはConnectionFactory()といったそのクラスのインスタンス生成コードを記述しています。単にクラスインスタンスを生成するだけなら、このコードで問題ありませんが、より複雑な処理を行いたい場合に利用するのが、provideDelegate()演算子です。
例えば、リスト7のコードです。
class ConnectionFactoryKai<Owner> { operator fun provideDelegate(thisRef: Owner, property: KProperty<*>): ConnectionPool<Owner> { connectionの生成処理 return ConnectionPool(connection) } }
リスト7では、委譲先クラスとしてConnectionPoolを想定しており、これは、例えばリスト8のコードです。
class ConnectionPool<Owner>( private val _connection: Connection ) { operator fun getValue(thisRef: Owner, property: KProperty<*>): Connection { return _connection } }
リスト8のように、ConnectionPoolクラス内ではConnectionを生成するのではなく、Connectionはあくまで他で生成し、生成されたConnectionを保持しながらその値を委譲元プロパティに委譲するものです。
この場合は、ConnectionPoolを生成するためのより上位の仕組みが必要で、provideDelegate()演算子をオーバーライドしたリスト7のようなクラスを用意します。
そして、provideDelegate()内で委譲先であるConnectionPoolインスタンスを生成するコードを記述します。なお、provideDelegate()の引数は、getValue()演算子と同じです。
こういったConnectionFactoryを用意したら、あとはリスト4と同様にbyの次にConnectionFactoryKai()を記述するだけです。
さらに、provideDelegate()が記述されたクラス(ここでの例ではConnectionFactoryKai)のインスタンスの生成時に別の処理が必要な場合は、リスト9の(2)のように、ConnectionFactoryKaiインスタンスそのものを生成する関数を用意し、(1)のように、その関数の実行コードをbyの次に記述することも可能です。
class DataProviderKai { private val _connection: Connection by createConnectionDelegate() // (1) private fun createConnectionDelegate(): ConnectionFactoryKai<DataProviderKai> { // (2) : return ConnectionFactoryKai() } }
なお、provideDelegateの仕組みに関して、演算子のオーバーライドだけでなく、インターフェースも利用できるようにバージョン1.4で変更されており、その場合はPropertyDelegateProviderインターフェースとします。
委譲プロパティまとめ
委譲プロパティに関して紹介してきました。ここまでの内容を表2にまとめておきます。
委譲先 | 内容 | 委譲コード |
---|---|---|
他のプロパティ | 他で定義されたプロパティと同内容のプロパティを用意する場合に適している | by 〇〇::プロパティ名 |
アクセサを自作したクラス | プロパティの生成処理や格納処理コードを自作し、再利用できる | by 委譲先クラス() |
自作オブジェクト | ReadOnlyProperty、または、ReadWritePropertyを実装したオブジェクトを用意する | by オブジェクト名 |
委譲先インスタンスを生成するクラス | provideDelegate()メソッドを定義したクラス(PropertyDelegateProviderクラス)を用意することで、その中に委譲先クラス(アクセサを自作したクラス)の生成処理を記述できる | by PropertyDelegateProviderクラス() |
PropertyDelegateProviderクラスインスタンスを生成する関数 | PropertyDelegateProviderクラスインスタンスを生成する際に、さらに別の処理が必要な場合にそれを関数にまとめ、その関数を委譲先とできる | by PropertyDelegateProviderインスタンス生成関数 |