Menu
CoddyTech

Đối tượng đồng hành

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 13 trên 57.

Một số thành viên thuộc về cả lớp thay vì thuộc về một đối tượng cụ thể. Kotlin đặt chúng trong một companion object bên trong lớp, và bạn gọi chúng thông qua tên lớp:

class Temperature(val celsius: Double) {
    companion object {
        fun fromFahrenheit(f: Double) = Temperature((f - 32) * 5 / 9)
    }
}

Bên trong main:

val boiling = Temperature.fromFahrenheit(212.0)
println(boiling.celsius)

Đầu ra:

100.0

fromFahrenheit là một hàm nhà máy: một hàm tạo ra các thực thể. Khác với hàm khởi tạo, hàm này có tên mô tả rõ ràng và có thể trả về null thay vì một đối tượng. Với private constructor, hàm nhà máy là cách duy nhất để tạo đối tượng, nên có thể kiểm tra và làm sạch đầu vào trước:

class Username private constructor(val text: String) {
    companion object {
        fun of(raw: String): Username? {
            val clean = raw.trim().lowercase()
            return if (clean.length in 3..12) Username(clean) else null
        }
    }
}

Bên trong main:

println(Username.of("  Ada_L ")?.text)
println(Username.of("x")?.text)
// Username("x")   // lỗi: hàm khởi tạo là private

Đầu ra:

ada_l
null

Đối tượng companion thuộc về lớp, nên nó có thể gọi hàm khởi tạo riêng tư, còn code của chính lớp dùng các thành viên của companion mà không cần tiền tố. Ở đây, mỗi vé lấy số tiếp theo từ một bộ đếm dùng chung cho tất cả vé:

class Ticket(val holder: String) {
    val number = nextNumber++

    companion object {
        private var nextNumber = 1
        val issued: Int
            get() = nextNumber - 1
    }
}

Bên trong main:

val a = Ticket("Ada")
val b = Ticket("Bo")
println("${a.holder} #${a.number}, ${b.holder} #${b.number}")
println("issued: ${Ticket.issued}")

Đầu ra:

Ada #1, Bo #2
issued: 2

Kotlin không có từ khóa static. Các thành viên của đối tượng đồng hành đảm nhiệm vai trò đó: chỉ có một bản cho cả lớp, được truy cập bằng ClassName.member. Giống như mọi đối tượng, đối tượng đồng hành được tạo một lần khi lớp được dùng lần đầu. Hãy đặt vào đó những gì không phụ thuộc vào một thực thể, chẳng hạn như các hàm tạo đối tượng và bộ đếm dùng chung; mọi thứ đọc dữ liệu của một thực thể vẫn là thành viên thông thường.

class User private constructor(val name: String) {
    fun greet() = "hi $name"             // cần một instance: user.greet()

    companion object {
        fun guest() = User("guest")      // thuộc về lớp (class): User.guest()
    }
}
challenge icon

Thử thách

Dễ

Hoàn thiện lớp Coupon. Hàm khởi tạo của lớp là private, vì vậy bạn tạo phiếu giảm giá bằng hàm factory Coupon.create(code, percent) trong đối tượng đồng hành. Hàm này trả về null nếu mã không gồm từ 4 đến 10 ký tự là chữ in hoa và chữ số, hoặc phần trăm không nằm trong khoảng từ 1 đến 90. Đối tượng đồng hành cũng đếm số phiếu giảm giá đã tạo trong created. Code bên ngoài có thể đọc giá trị này nhưng không thể thay đổi. apply(price) trả về giá sau khi trừ khoản giảm giá, làm tròn xuống (price * (100 - percent) / 100).

Code được cung cấp đọc các dòng SAVE10 10 200 (mã, phần trăm, giá). Code in ra SAVE10: 180 hoặc invalid coupon SAVE10, rồi cuối cùng in created: kèm theo số lượng.

Viết code của bạn trong Coupon.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 đã cho: giữ nguyên
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val (code, percent, price) = line.split(" ")
        val coupon = Coupon.create(code, percent.toInt())
        if (coupon == null) println("invalid coupon $code") else println("${coupon.code}: ${coupon.apply(price.toInt())}")
    }
    println("created: ${Coupon.created}")
}
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