The origin of delegation in programming is from object composition. Object composition is a way to combine simple objects to derive a complex one. Object compositions are a critical building block of many basic data structures, including the tagged union, the linked list, and the binary tree.
To make object composition more reusable (as reusable as inheritance), a new pattern is incorporated - the delegation pattern.
This pattern allows an object to have a helper object, and that helper object is called a delegate. This pattern allows the original object to handle requests by delegating to the delegate helper object.
Though delegation is an object-oriented design pattern, not all languages have implicit support for delegation (such as Java, which doesn't support delegation implicitly). In those cases, you can still use delegation by explicitly passing the original object to the delegate to a method, as an argument/parameter.
Over time, the delegation pattern has proven to be a better alternative of inheritance. Inheritance is a powerful tool for code reuse, especially in the context of the Liskov Substitution model. Moreover, the direct support of OOP languages makes it even stronger.
However, inheritance still has some limitations, such as a class can't change its superclass dynamically during program execution; also, if you perform a small modification to the super class, it'll be directly propagated to the child class, and that is not what we want every time.
Delegation, on the other hand, is flexible. You can think of delegation as a composition of multiple objects, where one object passes its method calls to another one and calls it a delegate.
For an example, think of the Electronics
class and Refrigerator
class. With inheritance, Refrigerator
should implement/override the Electronics
method calls and properties. With delegation however, the Refrigerator
object would keep a reference of the Electronics
object and would pass the method calls with it.
Kotlin 1.1 brought many updates; one of the important ones was delegated properties. There are three types of delegated properties:
lazy
: Lazy properties are the ones evaluated first and the same instance is returned after them, much like a cache.observable
: The listener is notified whenever a change is made.map
: Properties are stored in the map instead of in every field.Delegates are a great way to postpone object creation until we use it. This improves startup time (which is much needed and appreciated in Android).
First, we will work with the lazy delegate property. Simply put, this delegate can suspend the object creation until we first access it. This is really important when you are working with heavy objects; they take a long time to be created - for example, when creating a database instance or maybe dagger components. Not only this, the result is remembered and the same value is returned for subsequent calls for getValue()
on this kind of delegated property.
First, let's see how to create a property through lazy initialization. The syntax is as follows:
val / var <property name>: <Type> by <delegate>
While creating a lazy delegate, we use by lazy, as shown:
class MainActivity : AppCompatActivity() { private val textView : TextView by lazy { findViewById<TextView>(R.id.textView) as TextView } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textView.text="ABC" } }
The lazy delegate initializes the object on its first access and stores the value, which is then returned for subsequent accesses.
Let's take a look at an example:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val button by lazy { findViewById<Button>(R.id.submit_button) } setContentView(R.layout.activity_main) button.text="Submit" }
The preceding is a standard onCreate
method of an activity. If you look carefully, we have set the button
variable before the setContentView(..)
method. When you run it, it runs perfectly. If you hadn't used lazy
, it would have given a NullPointerException
.
Another key thing to note is that, by default, the evaluation of lazy
properties will be synchronized, which means the value is computed in one thread, and the rest of the threads will see the same value. There are three types of initialization: LazyThreadSafetyMode.SYNCHRONIZED
, LazyThreadSafetyMode.PUBLICATION
, LazyThreadSafetyMode.NONE
.
Another useful delegate is the observable delegate. This delegate helps us observe any changes to the property. For example, let's take a look at a very basic implementation of the observable delegate:
fun main(args: Array<String>) { val paris = Travel() paris.placeName="Paris" paris.placeName="Italy" } class Travel { var placeName:String by Delegates.observable("<>") {property, oldValue, newValue -> println("oldValue = $oldValue, newValue = $newValue") } }
This is the output:
oldValue = <>, newValue = Paris oldValue = Paris, newValue = Italy
As we can see, the observable
delegate takes in two things: a default value (which we specified as <>) and a handler, which gets called whenever that property is modified.
Let's now work with the vetoable delegate. It's a lot like the observable delegate, but with it, we can "veto" the modification. Let's look at an example:
fun main(args: Array<String>) { val paris = Travel() paris.placeName="Paris" paris.placeName="Italy" println(paris.placeName) } class Travel { var placeName:String by Delegates.vetoable("<>"){ property, oldValue, newValue -> if(!newValue.equals("Paris")){ return@vetoable false } true } }
This is the output:
Paris
As you can see in the preceding example, if newValue
isn't equal to "Paris", we will return false
, and the modification will be aborted. If you want the modification to take place, you need to return true
from the construct.
The vetoable()
takes an initial value, which could be an empty list, and also an onChange
callback, which is called before the change to a property is made. The callback returns true if the change is successful and false if it is vetoed.
Vetoable can be especially useful if you use it in the Recyclerview
adapter. Generally, you would assign data to the list directly and may call notifyDatasetChanged
, but this is highly inefficient, as it will result in loading all the data again. We can use vetoable to check whether the content is the same by matching the old value and new value and can veto modification if it is the same.
The observable delegated property can be used extensively in adapters. Adapters are used to populate data in some sort of list. Usually, when data is updated, we just update the member variable list in the adapter and then call notifyDatasetChanged()
. With the help of observable and DiffUtils
, we can just update the things that are actually changed, rather than changing everything. This results in much more efficient performance.
Sometimes, you create an object based on values dynamically, for example, in the case of parsing JSON. For those applications, we can use the map instance itself as the delegate for a delegated property. Let's see an example here:
fun main(args: Array<String>) { val paris = Travel(mapOf( "placeName" to "Paris" )) println(paris.placeName) } class Travel(val map:Map<String, Any?>) { val placeName: String by map }
Here's the output:
Paris
To make it work for var
properties, you need to use a MutableMap
, so the preceding example might look something like this:
fun main(args: Array<String>) { val paris = Travel(mutableMapOf( "placeName" to "Paris" )) println(paris.placeName) } class Travel(val map:MutableMap<String,Any?>) { var placeName: String by map }
The output will be the same.
What’s the difference between lazy and lateinit?
Both are used to delay the property initializations in Kotlin. However, there are significant differences between the two of them, listed as follows:
lazy {...}
delegate can only be used for val
properties; lateinit
can only be used for var
properties.lateinit var
property can't be compiled into a final field, and so you can't achieve immutability. A lateinit var
property has a backing field to store the value, whereas lazy {...}
creates a delegate object that acts as a container for the value once created and provides a getter for the property. If you need the backing field to be present in the class, you will have to use lateinit
.lateinit
property cannot be used for nullable properties or Java primitive types. This is a restriction imposed by the usage of null for uninitialized values.lateinit var
property is more flexible when it comes to where it can be initialized. You can set it up anywhere that the object is visible from. For lazy{...}
, it defines the only initializer for the property, which can be altered only by overriding it. The instantiation is thus known in advance, unlike a lateinit var
property, where if you use a dependency injection, for example, it can end up providing different instances of derived classes.val x: Int by lazy { 10 } lateinit var y: String