Menu
CoddyTech

Ủy quyền với by

Thuộc phần Lập trình hướng đối tượng trong hành trình Kotlin của Coddy. Bài học 46 trên 57.

Ủy quyền nghĩa là một đối tượng giao việc cho một đối tượng khác thay vì kế thừa từ đối tượng đó. Kotlin hỗ trợ trực tiếp cơ chế này: class Polite(private val inner: Greeter) : Greeter by inner triển khai Greeter bằng cách chuyển tiếp mọi lời gọi đến inner, và lớp chỉ ghi đè những gì nó thay đổi:

interface Greeter {
    fun hello(name: String): String
    fun bye(name: String): String
}
class English : Greeter {
    override fun hello(name: String) = "Hello, $name"
    override fun bye(name: String) = "Bye, $name"
}
class Polite(private val inner: Greeter) : Greeter by inner {
    override fun hello(name: String) = inner.hello(name) + ", pleased to meet you"
}

Bên trong main:

val g = Polite(English())
println(g.hello("Ada"))
println(g.bye("Ada"))

Đầu ra:

Hello, Ada, pleased to meet you
Bye, Ada

Ủy quyền là một cách khác thay cho kế thừa. Cách này hoạt động với các giao diện có phần triển khai là final, và đối tượng được ủy quyền có thể là bất kỳ đối tượng nào được chọn khi chương trình chạy. Tập hợp này đếm mọi lần gọi add và chuyển tiếp mọi thao tác khác đến một MutableSet thông thường:

class CountingSet<T>(private val inner: MutableSet<T> = mutableSetOf()) : MutableSet<T> by inner {
    var attempts = 0
        private set

    override fun add(element: T): Boolean {
        attempts++
        return inner.add(element)
    }
}

Bên trong main:

val tags = CountingSet<String>()
tags.add("kotlin")
tags.add("jvm")
tags.add("kotlin")
println("${tags.size} tags after ${tags.attempts} adds, contains jvm: ${"jvm" in tags}")

Đầu ra:

2 tags after 3 adds, contains jvm: true

Thuộc tính được ủy quyền giao getter và setter của một thuộc tính cho một đối tượng khác, cũng bằng by. lazy trong chương 2 là một delegate như vậy, và một map cũng có thể làm delegate: mỗi thuộc tính đọc mục có tên trùng với tên của nó:

class User(values: Map<String, Any?>) {
    val name: String by values
    val age: Int by values
}

Bên trong main:

val u = User(mapOf("name" to "Ada", "age" to 36))
println("${u.name} is ${u.age}")

Đầu ra:

Ada is 36

Bạn có thể tự viết một trình ủy quyền thuộc tính: một lớp có operator fun getValue, cùng với setValue cho var. Kotlin gọi chúng mỗi khi đọc hoặc ghi thuộc tính. Trình ủy quyền này loại bỏ khoảng trắng ở đầu và cuối mọi văn bản được gán, đồng thời đếm số lần thay đổi:

import kotlin.reflect.KProperty

class Trimmed {
    private var value = ""
    var changes = 0

    operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        value = newValue.trim()
        changes++
    }
}

class Form {
    val nameField = Trimmed()
    var name: String by nameField
}

Bên trong main:

val form = Form()
form.name = "  Ada  "
form.name = "Ada L. "
println("[${form.name}] after ${form.nameField.changes} changes")

Kết quả:

[Ada L.] after 2 changes
challenge icon

Thử thách

Dễ

Giao diện Storage (với put, get và keys) cùng phần triển khai MapStorage đã được cung cấp. Hãy viết hai lớp trang trí bằng cách ủy quyền cho lớp, mỗi lớp chỉ ghi đè put: LoggingStorage(inner) ghi lại put a=1 trong danh sách chỉ đọc log và chuyển tiếp lời gọi, còn LimitedStorage(inner, max) từ chối (trả về false) thêm khóa mới khi đã có max khóa, nhưng vẫn cho phép cập nhật các khóa hiện có. Mọi thứ khác đều được chuyển tiếp bằng by.

Code được cung cấp bọc một MapStorage trong LoggingStorage và, nếu dòng đầu tiên là giới hạn dương, bọc tiếp trong LimitedStorage. Code chạy các lệnh put a 1, get a và keys, in ra ok, refused, giá trị hoặc missing, cùng danh sách khóa, rồi cuối cùng in ra log: put a=1, ....

Code của bạn nằm trong LoggingStorage.kt, LimitedStorage.kt, Storage.kt và MapStorage.kt. Main.kt chứa code đầu vào/đầu ra đã được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

fun main() {
    // Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
    val input = generateSequence(::readLine).toList()
    val base = MapStorage()
    val logging = LoggingStorage(base)
    val limit = input[0].toInt()
    val storage: Storage = if (limit > 0) LimitedStorage(logging, limit) else logging
    for (cmd in input.drop(1)) {
        val p = cmd.split(" ")
        when (p[0]) {
            "put" -> println(if (storage.put(p[1], p[2])) "ok" else "refused")
            "get" -> println(storage.get(p[1]) ?: "missing")
            else -> println(storage.keys())
        }
    }
    println("log: " + logging.log.joinToString(", ").ifEmpty { "empty" })
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Playground Kotlin