Skip to Content

Delegation trong Kotlin

🎯 Mục tiêu: Hiểu Delegation - pattern “composition over inheritance” mạnh mẽ của Kotlin, bao gồm Class Delegation và Property Delegation.


💡 Delegation là gì?

Delegation là pattern ủy quyền công việc cho object khác thay vì tự làm. Kotlin hỗ trợ delegation ở cấp ngôn ngữ với keyword by.

// Thay vì tự implement, delegate cho object khác interface Printer { fun print(message: String) } class ConsolePrinter : Printer { override fun print(message: String) = println("[Console] $message") } // Logger delegate việc print cho printer class Logger(printer: Printer) : Printer by printer { // Tất cả methods của Printer được delegate cho printer // Không cần viết: override fun print(message: String) = printer.print(message) } fun main() { val logger = Logger(ConsolePrinter()) logger.print("Hello!") // [Console] Hello! }
💡

Composition over Inheritance: Thay vì kế thừa (is-a), sử dụng delegation (has-a) để code linh hoạt hơn.


📝 Class Delegation

Cú pháp cơ bản

interface SoundMaker { fun makeSound(): String } class Dog : SoundMaker { override fun makeSound() = "Woof!" } class Cat : SoundMaker { override fun makeSound() = "Meow!" } // RobotPet delegate SoundMaker cho soundMaker class RobotPet(soundMaker: SoundMaker) : SoundMaker by soundMaker { fun greet() = println("Hello! I say: ${makeSound()}") } fun main() { val dogRobot = RobotPet(Dog()) dogRobot.greet() // Hello! I say: Woof! val catRobot = RobotPet(Cat()) catRobot.greet() // Hello! I say: Meow! }

Override một phần

interface Printer { fun print(text: String) fun printLine(text: String) } class DefaultPrinter : Printer { override fun print(text: String) = print(text) override fun printLine(text: String) = println(text) } class FancyPrinter(printer: Printer) : Printer by printer { // Override chỉ một method override fun printLine(text: String) { println("✨ $text ✨") } // print() vẫn delegate cho printer } fun main() { val fancy = FancyPrinter(DefaultPrinter()) fancy.print("Hello ") // Hello (default) fancy.printLine("World") // ✨ World ✨ (overridden) }

Ví dụ thực tế: Decorator Pattern

interface DataSource { fun read(): String fun write(data: String) } class FileDataSource(private val filename: String) : DataSource { override fun read() = "Data from $filename" override fun write(data: String) = println("Writing to $filename: $data") } // Thêm logging bằng delegation class LoggingDataSource( private val source: DataSource ) : DataSource by source { override fun read(): String { println("[LOG] Reading...") return source.read().also { println("[LOG] Read complete") } } override fun write(data: String) { println("[LOG] Writing...") source.write(data) println("[LOG] Write complete") } } fun main() { val dataSource = LoggingDataSource(FileDataSource("data.txt")) dataSource.write("Hello") // [LOG] Writing... // Writing to data.txt: Hello // [LOG] Write complete }

🔧 Property Delegation

Kotlin cho phép delegate getter/setter của property cho object khác.

lazy - Khởi tạo chậm

val heavyObject: String by lazy { println("Computing...") Thread.sleep(1000) // Simulate heavy computation "Computed Result" } fun main() { println("Before accessing") println(heavyObject) // Computing... Computed Result println(heavyObject) // Computed Result (cached, không tính lại) }
ℹ️

lazy chỉ tính toán một lần khi lần đầu truy cập, sau đó cache kết quả.

observable - Theo dõi thay đổi

import kotlin.properties.Delegates class User { var name: String by Delegates.observable("Unknown") { prop, old, new -> println("$old -> $new") } var age: Int by Delegates.observable(0) { _, old, new -> if (new != old) { println("Age changed from $old to $new") } } } fun main() { val user = User() user.name = "Alice" // Unknown -> Alice user.name = "Bob" // Alice -> Bob user.age = 25 // Age changed from 0 to 25 }

vetoable - Từ chối giá trị không hợp lệ

import kotlin.properties.Delegates class Account { // Chỉ chấp nhận balance >= 0 var balance: Double by Delegates.vetoable(0.0) { _, _, newValue -> newValue >= 0 // Return true để chấp nhận, false để từ chối } } fun main() { val account = Account() account.balance = 100.0 println(account.balance) // 100.0 account.balance = -50.0 // Bị từ chối! println(account.balance) // 100.0 (không thay đổi) }

notNull - Lateinit cho primitive types

import kotlin.properties.Delegates class Config { // lateinit không dùng được với Int, dùng notNull thay thế var port: Int by Delegates.notNull() var timeout: Long by Delegates.notNull() } fun main() { val config = Config() // println(config.port) // ❌ IllegalStateException config.port = 8080 config.timeout = 30000L println("Port: ${config.port}") // Port: 8080 }

Delegate to Map

class User(map: Map<String, Any?>) { val name: String by map val age: Int by map val email: String? by map } fun main() { val userData = mapOf( "name" to "Alice", "age" to 25, "email" to "[email protected]" ) val user = User(userData) println("${user.name}, ${user.age}, ${user.email}") // Alice, 25, [email protected] }

🎯 Custom Property Delegate

Tạo delegate của riêng bạn:

import kotlin.reflect.KProperty class TrimDelegate { 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 TrimDelegate() var email: String by TrimDelegate() } fun main() { val form = Form() form.username = " alice " form.email = " [email protected] " println("[${form.username}]") // [alice] println("[${form.email}]") // [[email protected]] }

🛠️ Thực hành

Bài tập: Tạo CachedProperty delegate

// TODO: Tạo delegate cache giá trị với expiration time

Lời giải:

import kotlin.reflect.KProperty class CachedProperty<T>( private val expirationMs: Long, private val compute: () -> T ) { private var cachedValue: T? = null private var lastComputeTime: Long = 0 operator fun getValue(thisRef: Any?, property: KProperty<*>): T { val now = System.currentTimeMillis() if (cachedValue == null || now - lastComputeTime > expirationMs) { cachedValue = compute() lastComputeTime = now println("[Cache] Computed new value") } else { println("[Cache] Using cached value") } return cachedValue!! } } class DataService { val users: List<String> by CachedProperty(5000) { // Simulate API call listOf("Alice", "Bob", "Charlie") } } fun main() { val service = DataService() println(service.users) // [Cache] Computed new value println(service.users) // [Cache] Using cached value Thread.sleep(6000) println(service.users) // [Cache] Computed new value (expired) }

📱 Trong Android

// lazy initialization cho expensive views class MainActivity : AppCompatActivity() { private val heavyAdapter by lazy { println("Creating adapter") HeavyRecyclerAdapter() } private val database by lazy { Room.databaseBuilder( applicationContext, AppDatabase::class.java, "app-db" ).build() } } // ViewModel với observable class UserViewModel : ViewModel() { var userName: String by Delegates.observable("") { _, old, new -> if (new != old) { _userNameLiveData.value = new } } private val _userNameLiveData = MutableLiveData<String>() val userNameLiveData: LiveData<String> = _userNameLiveData } // SharedPreferences delegate fun SharedPreferences.string(key: String, default: String = "") = object : ReadWriteProperty<Any?, String> { override fun getValue(thisRef: Any?, property: KProperty<*>) = getString(key, default) ?: default override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { edit().putString(key, value).apply() } } class Settings(prefs: SharedPreferences) { var username by prefs.string("username") var theme by prefs.string("theme", "light") }

⚠️ Lưu ý quan trọng

⚠️

lazy thread-safety modes:

val default by lazy { "Default" } // SYNCHRONIZED (thread-safe, mặc định) val unsafe by lazy(LazyThreadSafetyMode.NONE) { "Unsafe" } // Không thread-safe, nhanh hơn val publication by lazy(LazyThreadSafetyMode.PUBLICATION) { "Publication" } // Cho phép multiple computation
💡

Khi nào dùng Delegation:

  • Class Delegation: Decorator pattern, flexible composition
  • lazy: Expensive initialization, chỉ tính khi cần
  • observable: Logging, UI updates khi data thay đổi
  • vetoable: Validation trước khi set

✅ Checklist - Tự kiểm tra

Sau bài học này, bạn có thể:

  • Hiểu pattern Delegation và lợi ích “composition over inheritance”
  • Sử dụng class delegation với by
  • Override một phần methods khi delegate
  • Sử dụng lazy cho lazy initialization
  • Sử dụng observable để theo dõi thay đổi
  • Sử dụng vetoable để validate trước khi set
  • Tạo custom property delegate

Tiếp theo: Data Class

Last updated on