スポンサーリンク

Kotlinでinterface + abstract classにおいて、再起呼び出しを含む場合に呼び出しInstanceと同クラスを返り値とする関数を一箇所で記述したい

スポンサーリンク

状況

以下のような状況です。compositeパターンを使用している場合に具象クラスを返答してほしい場合に(例えば、Compositeの生成途中等でまだInterfaceとして渡したくないが処理は一括したい等)、に利用したい場合があるのではないでしょうか。

interface IInterface{
    val children: List<IInterface>
    
    //呼び出し時のclassを返り値を持つ関数を作りたい
    //IInterfaceで呼び出した時⇒IInterfaceで返す
    //ChildClassで呼び出した時⇒ChildClassで返す
}

abstract class AbstractClass(
    override val children: List<AbstractClass> = mutableListOf()
) : IInterface{
    
}

class ChildClass(
    override val children: List<ChildClass> = mutableListOf()
) : AbstractClass(a)

対策

再起呼び出しありの場合

まずタイトル通りの場合(関数が再起的である場合)では、綺麗に抽象化及びrefiedして渡すことはできません。interfaceの外部にextension functionとして定義し、(必要な場合には)Unchecked castをsuppressするのが最もいい、というより、これしか実現できないと思います。

下記の通りのようになります。

 interface IInterface{
    val children: List<IInterface>
} 

fun <T :  IInterface> T.findChild(name: String): T? {
    @Suppress("UNCHECKED_CAST")
    for (child in (children as List<T>)) {
        val result = child.findChild(name)
        if (result != null) {
                return result
            }
        }
    return null
}

これで呼び出し元のClass(即ち、childrenの型に対応したClass)をT?として返答することが可能です。

ちなみにKotlinにおいてはListはtype covariantで定義されているので、List<String>はList<Object>のサブクラスです。(Javaではこれはサブクラスではありません。? extends Objectとか明示的に定義する必要があります)

再起呼び出しがない場合

interfaceの一部として定義したい

interfaceにはinline関数を定義することができないため、以下のようにどうしても@Suppress annotationが必要な形式となります。

  interface IInterface{
    val children: List<IInterface>

    fun <T : IInterface> T.findChild(name: String): T? {
        @Suppress("UNCHECKED_CAST") 
        for (child in (children as List<T>)) {
            val result = child
            if (result != null) {
                return result
            }
        }
        return null
    } 
}  

extension functionとして定義したい

再起呼び出しがない場合はinline + reified関数を利用すると、呼び出しInstanceの型情報を渡すことができるので、 Unchecked castをsuppressする必要がないのでもう少しきれいなコードにできます

inline fun <reified T : IInterface> T.findChild(name: String): T? {
    for (cat in children) {
        val result = cat.findChild(name) as T?
        if (result != null) {
            return result
        }
    }
    return null
}

*再起呼び出しの有無で分けたのはinline関数は再起呼び出しに対応していないためです

所感

Kotlinは柔軟性が高くプログラミングできるようになったので、候補数が多くなり唯一の手法というのが見つかりにくいです。どれがいいのか毎回ベストプラクティスを考える手間がある分のは楽しい反面、負担でもありますね

タイトルとURLをコピーしました