Delegation trong Kotlin
1. Giới thiệu
Delegation là một design pattern mạnh mẽ trong Kotlin, cho phép ủy quyền việc triển khai một interface hoặc property cho một object khác. Kotlin hỗ trợ delegation ở cấp độ ngôn ngữ với keyword by.
2. Class Delegation
Thay vì implement interface thủ công, có thể ủy quyền cho object khác:
interface Printer {
fun print(message: String)
}
class ConsolePrinter : Printer {
override fun print(message: String) {
println("[Console] $message")
}
}
// Delegation: ủy quyền cho printer
class Document(private val printer: Printer) : Printer by printer {
fun printDocument() {
print("Printing document...")
}
}
fun main() {
val consolePrinter = ConsolePrinter()
val doc = Document(consolePrinter)
doc.print("Hello") // [Console] Hello
doc.printDocument() // [Console] Printing document...
}Override một số methods
class LoggingDocument(private val printer: Printer) : Printer by printer {
override fun print(message: String) {
println("[LOG] About to print: $message")
printer.print(message) // Vẫn gọi delegate
}
}3. Property Delegation với by
Kotlin cung cấp cách ủy quyền việc get/set của property cho một delegate object.
Cú pháp
class MyClass {
var property: Type by Delegate()
}Delegate phải implement getValue() và setValue() (cho var).
4. by lazy - Lazy Initialization
Khởi tạo property khi được truy cập lần đầu tiên:
class ExpensiveResource {
init { println("Creating expensive resource...") }
}
class MyClass {
// Chỉ khởi tạo khi được truy cập lần đầu
val resource: ExpensiveResource by lazy {
println("Initializing...")
ExpensiveResource()
}
}
fun main() {
val obj = MyClass()
println("Object created")
// Lần đầu truy cập -> khởi tạo
println(obj.resource)
// Lần sau -> không khởi tạo lại
println(obj.resource)
}
// Output:
// Object created
// Initializing...
// Creating expensive resource...
// ExpensiveResource@...
// ExpensiveResource@...Lazy modes
// SYNCHRONIZED (default): thread-safe, chậm hơn
val a by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { "value" }
// PUBLICATION: nhiều thread có thể tính, nhưng chỉ 1 giá trị được dùng
val b by lazy(LazyThreadSafetyMode.PUBLICATION) { "value" }
// NONE: không thread-safe, nhanh nhất
val c by lazy(LazyThreadSafetyMode.NONE) { "value" }5. by observable - Observe Changes
Được gọi khi property thay đổi:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("Unknown") { property, oldValue, newValue ->
println("${property.name}: $oldValue -> $newValue")
}
var age: Int by Delegates.observable(0) { _, old, new ->
println("Age changed: $old -> $new")
}
}
fun main() {
val user = User()
user.name = "Alice" // name: Unknown -> Alice
user.name = "Bob" // name: Alice -> Bob
user.age = 25 // Age changed: 0 -> 25
}6. by vetoable - Validate Changes
Cho phép từ chối thay đổi:
import kotlin.properties.Delegates
class User {
// Chỉ chấp nhận age >= 0
var age: Int by Delegates.vetoable(0) { _, _, newValue ->
newValue >= 0 // true = accept, false = reject
}
// Chỉ chấp nhận name không rỗng
var name: String by Delegates.vetoable("Guest") { _, _, newValue ->
newValue.isNotBlank()
}
}
fun main() {
val user = User()
user.age = 25
println(user.age) // 25
user.age = -5 // Bị từ chối
println(user.age) // 25 (không đổi)
user.name = "Alice"
println(user.name) // Alice
user.name = "" // Bị từ chối
println(user.name) // Alice (không đổi)
}7. by map - Storing Properties in Map
Lưu properties trong Map, hữu ích cho JSON parsing:
class User(map: Map<String, Any?>) {
val name: String by map
val age: Int by map
val email: String by map
}
fun main() {
val data = mapOf(
"name" to "Alice",
"age" to 25,
"email" to "alice@example.com"
)
val user = User(data)
println(user.name) // Alice
println(user.age) // 25
println(user.email) // alice@example.com
}Mutable Map cho var properties
class MutableUser(map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
fun main() {
val data = mutableMapOf<String, Any?>(
"name" to "Alice",
"age" to 25
)
val user = MutableUser(data)
user.name = "Bob"
println(data["name"]) // Bob (map cũng bị thay đổi)
}8. Custom Delegate
Tạo delegate tùy chỉnh:
import kotlin.reflect.KProperty
class TrimmedString {
private var value: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
value = newValue.trim()
}
}
class Form {
var username: String by TrimmedString()
var email: String by TrimmedString()
}
fun main() {
val form = Form()
form.username = " alice "
form.email = " alice@example.com "
println("'${form.username}'") // 'alice'
println("'${form.email}'") // 'alice@example.com'
}Delegate với validation
import kotlin.reflect.KProperty
class NonNegative(private var value: Int = 0) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: Int) {
if (newValue < 0) {
throw IllegalArgumentException("${property.name} cannot be negative")
}
value = newValue
}
}
class Product {
var price: Int by NonNegative()
var quantity: Int by NonNegative()
}
fun main() {
val product = Product()
product.price = 100 // OK
product.quantity = 5 // OK
// product.price = -10 // Throws IllegalArgumentException
}9. Delegate với Provider Function
import kotlin.reflect.KProperty
class LoggedProperty<T>(private var value: T) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("Getting ${property.name}: $value")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
println("Setting ${property.name}: $value -> $newValue")
value = newValue
}
}
// Provider function
fun <T> logged(initialValue: T) = LoggedProperty(initialValue)
class Config {
var host: String by logged("localhost")
var port: Int by logged(8080)
}
fun main() {
val config = Config()
println(config.host) // Getting host: localhost
config.port = 3000 // Setting port: 8080 -> 3000
}10. Use Cases thực tế
SharedPreferences Delegate (Android)
class PreferenceDelegate<T>(
private val prefs: SharedPreferences,
private val key: String,
private val defaultValue: T
) {
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> prefs.getString(key, defaultValue) as T
is Int -> prefs.getInt(key, defaultValue) as T
is Boolean -> prefs.getBoolean(key, defaultValue) as T
else -> throw IllegalArgumentException("Unsupported type")
}
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
}
apply()
}
}
}
// Usage
class Settings(prefs: SharedPreferences) {
var username: String by PreferenceDelegate(prefs, "username", "")
var darkMode: Boolean by PreferenceDelegate(prefs, "dark_mode", false)
}Caching Delegate
class Cached<T>(private val compute: () -> T) {
private var cachedValue: T? = null
private var lastComputed: Long = 0
private val ttlMs: Long = 60_000 // 1 minute
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
val now = System.currentTimeMillis()
if (cachedValue == null || now - lastComputed > ttlMs) {
cachedValue = compute()
lastComputed = now
}
return cachedValue!!
}
}
fun <T> cached(compute: () -> T) = Cached(compute)
class DataService {
val expensiveData: List<String> by cached {
println("Computing expensive data...")
listOf("data1", "data2", "data3")
}
}📝 Tóm tắt
| Delegate | Mục đích |
|---|---|
by lazy | Lazy initialization, chỉ tính khi cần |
by observable | Observe khi property thay đổi |
by vetoable | Validate và có thể từ chối thay đổi |
by map | Lưu properties trong Map |
| Class delegation | Ủy quyền implement interface |
| Custom delegate | Tạo logic get/set tùy chỉnh |
Best practices:
- Dùng
lazycho expensive computations - Dùng
observablecho logging/debugging - Dùng
vetoablecho validation - Dùng class delegation thay vì inheritance khi có thể
Last updated on