Delegation và Destructuring trong Kotlin

Delegation (Ủy quyền)

Kotlin hỗ trợ mô hình ủy quyền như một thay thế cho kế thừa, giúp tái sử dụng mã nguồn hiệu quả hơn mà không cần mở rộng lớp.

Ủy quyền lớp

Một lớp có thể ủy quyền việc thực hiện giao diện cho một đối tượng khác thông qua từ khóa by.

interface Service {
    fun execute()
}

class RealService(val data: Int) : Service {
    override fun execute() = println("Thực thi với dữ liệu: $data")
}

class ProxyService(private val service: Service) : Service by service

fun main() {
    val real = RealService(42)
    ProxyService(real).execute() // In ra: Thực thi với dữ liệu: 42
}

Các phương thức đã được ủy quyền có thể bị ghi đè. Khi đó, chỉ phiên bản ghi đè mới được gọi — phần triển khai gốc sẽ bị bỏ qua.

class CustomService(service: Service) : Service by service {
    override fun execute() = println("Xử lý tùy chỉnh")
}

Lưu ý rằng thuộc tính hoặc phương thức bị ghi đè không ảnh hưởng đến hành vi của đối tượng được ủy quyền.

Ủy quyền thuộc tính

Thuộc tính cũng có thể được ủy quyền cho một đối tượng xử lý logic đọc/ghi.

class Example {
    var value: String by StringDelegate()
}

class StringDelegate {
    operator fun getValue(instance: Any?, property: KProperty<*>): String {
        return "Đã ủy quyền thuộc tính '${property.name}' từ $instance"
    }

    operator fun setValue(instance: Any?, property: KProperty<*>, newValue: String) {
        println("Gán '$newValue' cho '${property.name}' trên $instance")
    }
}

val example = Example()
println(example.value) // In ra thông điệp từ getValue
example.value = "Mới"   // Gọi setValue

Các trình ủy quyền thuộc tính chuẩn

  • Lazy: Giá trị chỉ được tính toán lần đầu khi truy cập.
val computedValue: String by lazy {
    println("Tính toán lần đầu...")
    "Kết quả"
}

fun main() {
    println(computedValue) // Tính toán + in kết quả
    println(computedValue) // Chỉ in lại, không tính toán lại
}
  • Observable: Theo dõi thay đổi giá trị.
import kotlin.properties.Delegates

class Monitor {
    var status: String by Delegates.observable("<khởi tạo>") { _, old, new ->
        println("Thay đổi: $old → $new")
    }
}

fun main() {
    val monitor = Monitor()
    monitor.status = "Bắt đầu"
    monitor.status = "Hoàn thành"
}
  • Map-based delegation: Lưu trữ thuộc tính trong Map.
class User(config: Map<String, Any?>) {
    val username: String by config
    val age: Int by config
}

val user = User(mapOf(
    "username" to "alice",
    "age" to 30
))
println(user.username) // alice

Phiên bản có thể sửa đổi dùng MutableMap tương tự.

  • Ủy quyền giữa các thuộc tính: Một thuộc tính có thể ủy quyền cho thuộc tính khác.
var globalCounter = 0

class Counter {
    var current: Int by ::globalCounter
    var backup: Int by this::current
}

Tính năng này hữu ích để chuyển đổi dần giữa các tên biến cũ – mới.

class NewCounter {
    var count: Int = 0
    @Deprecated("Dùng 'count'", ReplaceWith("count"))
    var totalCount: Int by this::count
}

Ủy quyền thuộc tính cục bộ

Có thể áp dụng lazy cho biến cục bộ bên trong hàm.

fun process(expensiveInit: () -> Data) {
    val cachedData by lazy(expensiveInit)
    if (needsProcessing && cachedData.isValid()) {
        cachedData.process()
    }
}

Tạo trình ủy quyền tùy chỉnh

Để hỗ trợ thuộc tính chỉ đọc, định nghĩa getValue:

class LoggerDelegate {
    operator fun getValue(instance: Any?, property: KProperty<*>): Logger {
        return Logger.getInstance(property.name)
    }
}

class MyClass {
    val log: Logger by LoggerDelegate()
}

Với thuộc tính có thể ghi, thêm setValue:

class MutableDelegate {
    private var storedValue: String = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return storedValue
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        storedValue = value
    }
}

Destructuring Declarations (Phân rã)

Cho phép tách một đối tượng thành nhiều biến riêng biệt.

data class Person(val name: String, val age: Int)

val person = Person("Bob", 28)
val (fullName, years) = person
println(fullName) // Bob
println(years)    // 28

Bản chất là gọi các hàm componentN() ẩn sau:

val fullName = person.component1()
val years = person.component2()

Hỗ trợ trong vòng lặp:

for ((key, value) in map.entries) {
    println("$key → $value")
}

Giải phóng biến không cần dùng bằng dấu gạch dưới:

val (_, status) = getResult()

Sử dụng trong lambda:

map.mapValues { (_, value) -> "[$value]" }

Có thể chỉ định kiểu rõ ràng:

map.mapValues { (_, text): Map.Entry<Int, String> -> text.uppercase() }

Kiểm tra và Chuyển đổi Kiểu

Dùng is để kiểm tra kiểu:

if (item is String) {
    println(item.length) // item được ép kiểu ngầm
}

Kotlin thực hiện ép kiểu thông minh — nếu đã kiểm tra is, biến được dùng trực tiếp mà không cần ép kiểu thủ công.

fun handle(value: Any) {
    if (value !is String) return
    println(value.length) // value đã là String
}

Dạng ngắn gọn với when:

when (input) {
    is Int -> println("Số nguyên: $input")
    is String -> println("Chuỗi: ${input.trim()}")
    is Array<*> -> println("Mảng độ dài: ${input.size}")
}

Ép kiểu an toàn với as? để tránh ngoại lệ:

val str: String? = unknownValue as? String

Từ khóa This với nhãn

this trong Kotlin có thể mang nhãn để phân biệt ngữ cảnh.

class Outer {
    inner class Inner {
        fun run() {
            println(this@Outer) // Tham chiếu lớp ngoài
            println(this)       // Tham chiếu lớp Inner
        }
    }
}

Trong hàm mở rộng:

fun Int.calculate() {
    val result = this * 2 // this là số nguyên gốc
}

Chọn giữa hàm thành viên và hàm cấp cao:

fun printInfo() = println("Hàm toàn cục")

class Printer {
    fun printInfo() = println("Hàm thành viên")
    fun invoke(useLocal: Boolean) {
        if (useLocal) this.printInfo() // Gọi hàm thành viên
        else printInfo()              // Gọi hàm toàn cục
    }
}

Thẻ: Kotlin delegation destructuring type-casting this-expression

Đăng vào ngày 23 tháng 6 lúc 16:35