🤝 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à gì?
Phần tiêu đề “💡 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ácinterface Printer { fun print(message: String)}
class ConsolePrinter : Printer { override fun print(message: String) = println("[Console] $message")}
// Logger delegate việc print cho printerclass 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!}📝 Class Delegation
Phần tiêu đề “📝 Class Delegation”Cú pháp cơ bản
Phần tiêu đề “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 soundMakerclass 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
Phần tiêu đề “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
Phần tiêu đề “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 delegationclass 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
Phần tiêu đề “🔧 Property Delegation”Kotlin cho phép delegate getter/setter của property cho object khác.
lazy - Khởi tạo chậm
Phần tiêu đề “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)}observable - Theo dõi thay đổi
Phần tiêu đề “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ệ
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)}notNull - Lateinit cho primitive types
Phần tiêu đề “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
Phần tiêu đề “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, )
val user = User(userData) println("${user.name}, ${user.age}, ${user.email}") // Alice, 25, [email protected]}🎯 Custom Property Delegate
Phần tiêu đề “🎯 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 "
println("[${form.username}]") // [alice]}🛠️ Thực hành
Phần tiêu đề “🛠️ 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
Phần tiêu đề “📱 Trong Android”// lazy initialization cho expensive viewsclass 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 observableclass 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 delegatefun 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
Phần tiêu đề “⚠️ Lưu ý quan trọng”✅ Checklist - Tự kiểm tra
Phần tiêu đề “✅ 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