Skip to Content
Kotlin📘 Ngôn ngữ KotlinDelegation (by lazy, by observable)

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()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

DelegateMục đích
by lazyLazy initialization, chỉ tính khi cần
by observableObserve khi property thay đổi
by vetoableValidate và có thể từ chối thay đổi
by mapLưu properties trong Map
Class delegationỦy quyền implement interface
Custom delegateTạo logic get/set tùy chỉnh

Best practices:

  • Dùng lazy cho expensive computations
  • Dùng observable cho logging/debugging
  • Dùng vetoable cho validation
  • Dùng class delegation thay vì inheritance khi có thể
Last updated on