Type cast from delegation

Hello. I’m developing Android app and sometimes hard to avoid Fat Activity(aka God Activity).

interface Interface1 {
    fun f11()
    fun f12()
    fun f13()
}
class FatActivity: Activity, Interface1 {
    override fun f11() {}
    override fun f12() {}
    override fun f13() {}
}

What’s worse, if I want to refactor, I can’t ab-test between old one and new one
because Activity is written in Manifest with scheme.
So I decided to delegate implementation.

interface Delegate {
    fun getDelegate(): Interface1
}
class SlimActivity: Activity, Delegate {
    private lateinit var delegation: Interface1
    override fun getDelegate() = delegation
}

Code became slimmer and ready to ab-test the implementation.
but I got frustrated by runtime-typecheck api.

fun useInterface1(o: Activity) { // api signature fixed
    (o as? Interface1)?.f11()
}

I googled and finally found the solution with reified.

inline fun <reified T : Any> getIntf(o: Any): T? {
    if (o is T) {
        return o
    } else if (o is Delegate) {
        val d = o.getDelegate()
        if (d is T) // I wanted to call getIntf recursively but inline doesn't support recursive.
            return d
    }
    return null
}

fun useInterface1(o: Activity) { // api signature fixed
    getIntf<Interface1>(o)?.f11()
}

What I suppose is the generalized version of this getIntf.
maybe with fancy syntax like… as, by … I don’t know…

Kotlin is a delegation-friendly language and this can make it more.

I understood your case until useInterface1() point. Both FatActivity and SlimActivity have strong typed access to f11(), so runtime checks should not be needed.

If you mean that you sometimes need to perform some operation on any possible Activity then yes, you need casts. But this is not really related to your delegation/composition - it would be the same if you would have MyActivity with method foo(). Generally, it is discouraged to design the code in the way that such checks are needed, but sometimes it is hard to avoid.

Also, what you did here is not a typical delegation, but just a composition. The main point of delegation is to implement Interface1 directly (e.g. FatActivity), but still delegate to a separate object. Delegation is hidden from the caller, it is a matter of implementation details. I can provide you an example, but I’m not sure if delegation is really your main concern here.

1 Like

Thank you for correction. You are right about delegation.
I should have titled composition/decomposition instead of delegation.

What I’m trying is ‘Composition over Inheritance’ migration.
FatActivity inherited Interface1 and SlimActivity composed Interface1.
During migration, useInterface1 should support both.

So I wish if Kotlin had something like getIntf (let’s say ‘as2’)
I could simply change ‘o as? Interface1’ to ‘o as2? Interface1’.

First of all, note that your question is partially about the code design/architecture, so what I’ll say here is only my opinion.

Second, it really depends on why do you need to decompose FatActivity, what is your main concern. Is it that this class contains too much code, it has too many responsibilities, you need to make it more modular, so you can swap parts of its behavior (e.g. Interface1), etc.? Or maybe it has too complicated API, too many methods and it is hard to use it?

If API is not your concern and you would prefer to keep it as is then you can do this while still decomposing the activity. You do this using… delegation. But the real one :wink:

class SlimActivity private constructor(
    interface1: Interface1
): Activity, Interface1 by interface1 {
    constructor() : this(createInterface1())
}

If you use property-based dependency injection to acquire interface1 then I think you can’t use automatic Kotlin delegation. You need to delegate all methods manually.

If you prefer to decompose the API itself then I think your approach is perfectly fine. I would probably make getIntf() an extension function instead and use when statement. If you like one-liners then you can also do:

return o as? T
    ?: (o as? Delegate)?.getDelegate() as? T

Also, if you don’t have very many similar interfaces to Interface1 then making this function generic seems like overengineering to me. getInterface1(), getInterface2(), etc. would be fine.

And last note: I probably don’t see a full picture here, but why do you need to A/B test the decomposition of the activity? I thought A/B testing is used to easily compare two different user interfaces or application behaviors, but what you do here is refactoring. And refactoring should be separated from any functional changes.

1 Like

You got all the point.

  • I wanted to decompose the class and android ‘Activity’ class doesn’t provide constructor(because the instance is created by os)
  • I like your one-liner with extension idea.
  • our company sometimes use a/b system for server-driven on/off switch, in case refactoring causes app crash.
    Thanks for your feedback.
1 Like