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 timeLờ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
lazycho 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