Kotlin delegated property
Giới thiệu
Với các property trong một class, chúng ta bắt buộc phải khởi tạo chúng hoặc sử dụng constructor để gán giá trị ngay khi khởi tạo đối tượng. Tuy nhiên, đôi lúc chúng ta lại muốn khởi tạo những property này chỉ khi truy cập lần đầu tiên vào property đó. Để thực hiện điều này, Kotlin cung cấp delegated property thông qua những chức năng sau:
lazy property: Giá trị của property được tính toán trong lúc truy cập lần đầu tiên đến property
observable property: lắng nghe sự thay đổi giá trị của property
Lưu trữ các property trong một map thay vì lưu riêng từng property Một VD của delegated property:
Cú pháp để khai báo delegated property là: val/var <property name>: <Type> by <expression>
. Biểu thức ở sau by
là delegate, bởi vì get()
(và set()
) tương ứng với property sẽ được ủy thác cho các method getValue()
và setValue()
của class Delegate
. Delegate
không cần phải implement bất kỳ interface nào nhưng phải cung cấp 2 function là getValue()
(và setValue()
đối với property kiểu var
). Ví dụ:
Khi chúng ta truy cập để đọc giá trị của p
, không phải function getter của property p
được gọi mà là function getValue()
của class Delegate
được gọi bởi property đã ủy thác việc này cho Delegate
. 2 tham số của function getValue()
lần lượt là đối tượng chứa p
và đối tượng lưu trữ các thông tin của p
. Bởi vậy, khi gọi p
:
Kết quả nhận được sẽ là:
Tương tự như vậy, khi chúng ta gán giá trị cho p
, function setValue()
sẽ được gọi. Các tham số của function setValue()
tương tự như function getValue()
, tham số thứ 3 là giá trị được gán cho p
:
Lưu ý: từ Kotlin 1.1, bạn có thể khai báo một delegated property ngay bên trong một function hoặc một block code, nó không cần là một member của class.
Delegate chuẩn
Thư viện chuẩn của Kotlin cung cấp các factory method cho nhiều mục đích sử dụng delegate khác nhau.
lazy
lazy() là một function lấy một lambda và trả về một instance của Lazy<T>
để có thể implement một lazy property: lần gọi đầu tiên đến function get()
sẽ thực thi đoạn code trong lambda được truyền vào để khởi tạo kết quả và gán kết quả cho property. Các lần gọi get()
sau sẽ chỉ trả về giá trị của property.
Kết quả được in ra là:
Mặc định, việc tính toán của lazy property được synchronized: giá trị được tính toán chỉ trong 1 thread, và tất cả các thread sẽ sử dụng cùng kết quả đó. Nếu quá trình synchronize của việc khởi tạo không được yêu cầu, nhiều thread sẽ có thể thực thi việc khởi tạo này cùng một lúc bằng cách truyền tham số LazyThreadSafetyMode.PUBLICATION
cho function lazy()
. Và nếu bạn chắc chắn rằng việc khởi tạo sẽ luôn luôn xảy ra trên một thread duy nhất, bạn có thể sử dụng tham số LazyThreadSafetyMode.NONE
, điều này giúp giảm chi phí (overhead) trong việc đảm bảo thread-safety và các chi phí khác
Observable
Delegate.observable() nhận vào 2 tham số: giá trị khởi tạo của property và một handler trong trường hợp property thay đổi giá trị. Handler mà chúng ta truyền vào sẽ được thực thi mỗi lần chúng ta gán giá trị cho property (sau khi việc gán được thực thi). Handler này có 3 tham số: property được gán, giá trị cũ và giá trị mới:
Kết quả được in ra là:
Nếu bạn muốn có thể can thiệp vào việc gán và phủ quyết việc đó, sử dụng vetoable() thay vì observable()
. Hanlder được truyền vào vetoable
sẽ được gọi trước khi việc gán được thực thi.
Lưu trữ property trong một map
Một trường hợp phổ biến là lưu trữ các giá trị của các property bên trong một map. Việc này thường hữu dụng trong trong trường hợp như parsing JSON hoặc làm một thứ gì đó động. Trong trường hợp này, bạn có thể sử dụng chính instance của map như là delegate cho một delegate property
Chúng ta có thể khởi tạo object bằng cách truyền vào một map. Khi truy cập đến property, việc này sẽ được ủy thác cho map
Với kiểu var
, chúng ta phải sử dụng MutableMap
thay vì Map
(read-only Map
)
Local delegated property (từ Kotlin 1.1)
Bạn có thể khai báo một local variable như là một delegated property. VD:
Biến memoizedFoo
sẽ chỉ được tính toán vào lần truy cập đầu tiên bằng lambda computeFoo
được truyền vào. Nếu someCondition
bằng false
, memoizedFoo
sẽ không được khởi tạo.
Yêu cầu của delegated property
Đây là những yêu cầu của delegated object được tổng kết lại:
Với read-only property (val
), một delegate phải implement một function tên là getValue
gồm 2 tham số:
thisRef
- phải có cùng kiểu hoặc kiểu mà kiểu của property kế thừaproperty
- phải có kiểuKProperty<*>
hoặc kiểu nó kế thừa Cùng với đó, functiongetValue()
phải trả về giá trị cùng kiểu với property.
Với mutable property ('var'), một delegate ngoài phải cung cấp getValue
như read-only property còn phải implement function setValue
có các tham số sau:
thisRef
- tương tự nhưgetValue
property
- tương tự nhưgetValue
giá trị mới - phải có cùng kiểu với property hoặc có kiểu mà kiểu của property kế thừa
getValue
và/hoặcsetValue
có thể là member function của delegate class hoặc extension function. Trường hợp phía sau là tiện hơn khi bạn cần ủy thác property cho một object mà ban đầu không implement các hàm này. Cả 2 function cần được đánh dấu với từ khóaoperator
Delegate class có thể implement interface ReadOnlyProperty
với val
hoặc ReadWriteProperty
với var
được khai báo trong thư viện chuẩn của Kotlin:
Luật chuyển đổi
Thực tế, với mỗi delegated property, Kotlin compiler sinh ra các property hỗ trợ và delegate cho property đó. Ví dụ, với property prop
, property ẩn prop$delegate
sẽ được sinh ra, và đoạn code của các các hàm getter, setter đơn giản là delegate đển property ẩn này:
Kotlin compiler cung cấp tất cả các thông tin cần thiết về prop
trong các tham số: this
tham chiếu đến một instance của class C
và this::prop
là reflection object của kiểu KProperty
, mô tả chính prop