Delegowanie właściwości – leniwe wartościowanie
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.