Saturday, 25 December 2021

Kotlin Delegation

In object-oriented programming, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender).

Delegation is a design pattern in which an object handles a request by delegating to a helper object, called the delegate. The delegate is responsible for handling the request on behalf of the original object and making the results available to the original object.

Kotlin supports “delegation” design pattern by introducing a new keyword “by”. Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object

Delegation should be used when:

  • When your subclass violates the Liskov substitution principle.  For example, when we are dealing with situations where inheritance was implemented only to reuse code of the superclass, but it is not really acting like it.
  • When the subclass uses only a portion of the methods of the superclass. In this case, it is only a matter of time before someone calls a superclass method that they were not supposed to call. Using delegation, we reuse only methods we choose (defined in the interface).
  • When we cannot or we should not inherit, because:
    • The class is final
    • It is not accessible and used from behind interface
    • It is just not designed for inheritance
Example: It will be very easy and self explanatory code snippet, here we have an interface for image loader and two other implementation with Glide and COIL Library.  There will be a delegation done through the ImageLoad class using "by" keyword.  

interface ImgSrcLoader {
    val loaderName: String
    fun onLoad()
    fun transition()
}

class GlideImpl(private val url: String, private val view: Any) : ImgSrcLoader {
    override val loaderName: String
        get() = "Glide"

    override fun onLoad() {
        println("Implementation with $loaderName : $url")
    }

    override fun transition() {
        println("Implementation with $loaderName transition Here")
    }
}

class CoilImpl(private val url: String, private val view: Any) : ImgSrcLoader {
    override val loaderName: String
        get() = "COIL"

    override fun onLoad() {
        println("Implementation with $loaderName : $url")
    }

    override fun transition() {
        println("Implementation with $loaderName transition Here")
    }
}

class ImageLoad(private val iLoader: ImgSrcLoader) : ImgSrcLoader by iLoader {
    /* This property change will not reflect/accessed on
     the super class implementations.*/
    override val loaderName: String
        get() = "NA"

    override fun transition() {
        if (iLoader is CoilImpl) {
            iLoader.transition()
            return
        }
        println("Transition with ${iLoader.loaderName} is restricted")
    }
}

fun main() {
    val url = "http://yourapp.photos.now/photo/0"

    ImageLoad(GlideImpl(url, "")).apply {
        transition()
        onLoad()
        println("ImageLoad: $loaderName")
    }

    ImageLoad(CoilImpl(url, "")).apply {
        transition()
        onLoad()
        println("ImageLoad: $loaderName")
    }
}

Output:

Transition with Glide is restricted
Implementation with Glide : http://yourapp.photos.now/photo/0
ImageLoad: NA
Implementation with COIL transition Here
Implementation with COIL : http://yourapp.photos.now/photo/0
ImageLoad: NA

Happy Coding :-)