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