Bỏ qua để đến nội dung

🤝 Delegation

🎯 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à 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!
}

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!
}
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)
}
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
}

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

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)
}
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ệ

Phần tiêu đề “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)
}
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
}
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]
}

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]]
}

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

// 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")
}


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