Kotlinにはクラスの委譲とプロパティの委譲の二つがありますがどちらも便利です!
これらを使っていろいろ学んだことがあるのでまとめておきます
Class Delegation
インターフェースの実装を他のクラスに委譲できるのがClass Delegationです
interface ITest{
fun a():String
}
class Test(val value:String):ITest{
override fun a() = value
}
class ClassDelegation1:ITest by Test("test1") {
}
class ClassDelegation2(test:Test):ITest by test{
}
このような感じです
ClassDelegation1のように委譲の宣言のとこでインスタンスを作成する方法かClassDelegation2のようにコンストラクタの引数を渡す方法の2通りになるのではないかと思います(個人的にはClassDelegation1の方が使いやすいです)
注意しときたいのはClass Delegationはプライマリコンストラクタでしか使えないとこです
class A{
constructor()
constructor(a:String)
}
class B(b:String?=null){
}
Aのようなクラスには使えないのでBのようにデフォルト値を与えるなどしてプライマリコンストラクタに押し込まないといけません
Class Delegationを使うと何がいいか?
何個もインターフェースを実装するなどの場合クラスの肥大化を抑え、なおかつ実装部分の共通コード化ができます(これに関してはインターフェースのデフォルト実装でも解決できますがインターフェースには好き勝手にプロパティをつけたりコンストラクタを用意できない点で差があると思います)
補足)インターフェースにプロパティを定義することはできますがインターフェースを実装するクラスでプロパティの実装をする必要が出てきます
追記)インターフェースのデフォルト実装とクラスの委譲の違いをまとめてみました
Property Delegation
公式ではDelegated PropertiesとかProperty Delegateと書かれてますね(にじみ出る英語苦手感)
プロパティの処理を他のクラスに委譲することができます
これにはstdlibで定義されてるものがあります
class DefinedPropertyDelegation {
var nullableString1: String by Delegates.notNull()
var nullableString2 by Delegates.notNull<String>()
var nullableString3: String by Delegates.notNull<String>()// これは丁寧すぎますね
}
このような書き方をします
byの後ろにはReadOnlyPropertyインターフェースかReadWritePropertyインターフェースを実装したものがきます
Delegates.notNull()は初期化をコンストラクタの処理の後でしたい場合に便利です
他にもlazy,observable,mapがあるみたいですが使ったことないのでどんな感じかはわからないです
自作する場合
先ほども述べましたがReadOnlyPropertyインターフェースかReadWritePropertyインターフェースを実装したものが必要になります
- ReadOnlyPropertyインターフェースがval
- ReadWritePropertyインターフェースがvar
っていう感じですね
// Tのvarプロパティ(中身は非nullを想定)
class ReadWriteTestProperty<R, T>(var value: T? = null) : ReadWriteProperty<R, T> {
var getCount = 0
var setCount = 0
override fun getValue(thisRef: R, property: KProperty<*>): T {
require(value != null)
getCount++
return value!!
}
override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
this.value = value
setCount++
}
}
// Tのvalプロパティ(中身は非nullを想定)
class ReadOnlyTestProperty<R, T>(var value: T? = null) : ReadOnlyProperty<R, T> {
var getCount = 0
override fun getValue(thisRef: R, property: KProperty<*>): T {
require(value != null)
getCount++
return value!!
}
}
// null許容のTのvalプロパティ
class ReadOnlyOptionalTestProperty<R, T>(var value: T? = null) : ReadOnlyProperty<R, T?> {
var getCount = 0
override fun getValue(thisRef: R, property: KProperty<*>): T? {
getCount++
return value!!
}
}
class PropertyDelegation() {
val test1: String by ReadOnlyTestProperty("a")
var test2: String by ReadWriteTestProperty("a")
val test3: String? by ReadOnlyOptionalTestProperty()
val test4: String by ReadWriteTestProperty("a")//あれっこないだまで…
}
Rはプロパティを宣言しているクラスの型(?)でTはプロパティの型です
上記のソースではNullableかどうかで処理を分けたりしてますがジェネリクスなので設計次第でなんとかなりそうな感じはします
それとこないだまではvalにReadWritePropertyインターフェースを使うとエラーが出てたような気がするのですが使えるようになったのかな?
(まぁインターフェースなのでどちらも実装すればいいだけですけどね)
あとbyのあとでいちいちコンストラクタ呼ぶのめんどくさくなったりするのでメソッド化するといいです
- クラスにメソッドを作る
- 拡張関数をクラスに対して作る
- コンパニオンオブジェクトにメソッドを作る
- 拡張関数をコンパニオンオブジェクトに対して作る
の方法があるかと思いますが拡張関数の2通りがライブラリ作成的には便利かと思います
Property Delegationの使いどころ
定義済みのをそのまま使うのもありですがライブラリ作成時に重宝できるかと思います
これを使ったものとしてKotter Knifeは有名かと思います
さいごに
Delegationを使ったものとしてJsonを扱いやすくするHKJsonを作りました(完全に自分向けですがw)
Delegationについて他に良い設計などがありましたらコメントくれるとありがたいです