Delegowanie funkcji jest smaczkiem w składni, który ułatwia nam życie i upraszcza kod. Znacznie ciekawszym rozwiązaniem jest delegowanie własności. Dziś omówimy wykorzystane tego mechanizmu przy leniwym wartościowaniu.

O co chodzi?

Często mamy do czynienia z klasami, które mają „ciężkie” pola. Ich inicjacja jest zasobożerna, a co więcej nie zawsze konieczna. W takim przypadku powinniśmy wykorzystać mechanizm leniwej inicjacji/wartościowania. Idealnie sprawdza się on w przypadku ORM-ów, gdzie pole jest pobierane z bazy danych w momencie wykorzystania, a nie odczytu obiektu.

Kotlin ma mechanizm pozwalający na pisanie leniwych inicjatorów tak, by do minimum ograniczyć ilość dodatkowego kodu.

Mechanizm

Zdefiniujmy klasę Employee, która posiada dwa pola name i boss. Pole name jest zwykłym String-iem, a boss będzie dociągane z „bazy danych”:

Listing 1. Definicja klasy Employee

class Employee(val name:String){
    val boss:Employee? by EmployeeDao()

    override fun toString(): String{
        return "Employee(name='$name', boss='$boss')"
    }
}

Konstrukcja by EmployeeDao(), jest właśnie takim leniwym inicjatorem. W momencie pierwszego odczytu pola boss zostanie wywołana metoda getValue klasy EmployeeDao. Metoda ta ma pewne ograniczenia. Przyjrzyjmy się nim:

Listing 2. Definicja EmployeeDao

class EmployeeDao{
    operator fun getValue(employee: Employee, property: KProperty<*>): Employee? {
        if(employee.name != "Krzysztof Jarzyna ze Szczecina")
            return Employee("Krzysztof Jarzyna ze Szczecina")
        
        return null;
    }
}

Jest operatorem, ma dwa parametry. Pierwszy to obiekty, którego właściwość ustawiamy (tu obiekt Employee o którego pole boss pytamy), a drugi to meta informacje o właściwości.

Jeżeli pole jest zmienne (var nie val), to klasa do której delegowano musi posiadać metodę setValue. Jej sygnatura jest podobna do getValue, z tą różnicą, że przyjmuje jeszcze jeden dodatkowy parametr, który jest wartością pola.

I na koniec krótka demonstracja:

Listing 3. Przykładowe wykorzystanie

fun main(args: Array<String>) {
    val jaś = Employee("Jaś");
    println(jaś.toString())

    val boss = jaś.boss
    println(boss.toString())
}

Podsumowanie

Mechanizm ten ma tylko jedną, maleńką wadę – nie działa z data class.