Menu
CoddyTech

Lớp trừu tượng

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

Lớp abstract là lớp cha không thể tự khởi tạo. Lớp này có thể khai báo các thành viên abstract không có thân hàm, và mọi lớp con cụ thể đều phải triển khai chúng:

abstract class Shape(val name: String) {
    abstract fun area(): Double

    fun describe() = "$name with area ${area()}"
}
class Circle(val r: Double) : Shape("circle") {
    override fun area() = 3.14 * r * r
}
class Rect(val w: Double, val h: Double) : Shape("rect") {
    override fun area() = w * h
}

Bên trong main:

val shapes: List<Shape> = listOf(Circle(1.0), Rect(2.0, 3.0))
for (s in shapes) println(s.describe())

Đầu ra:

circle with area 3.14
rect with area 6.0

Lớp trừu tượng mặc định là open mà không cần từ khóa này, các thành viên trừu tượng của nó cũng vậy, nên các lớp con chỉ cần viết override. Bản thân lớp trừu tượng không thể biên dịch thành công nếu bạn tạo một thực thể từ lớp đó, và lớp con không khai báo một thành viên trừu tượng thì cũng phải là lớp trừu tượng. Hãy dùng lớp trừu tượng khi lớp cha không có hành vi phù hợp cho một số thành viên:

// val s = Shape("blob")                       // lỗi: không thể tạo instance của lớp trừu tượng
// class Blob : Shape("blob")                  // lỗi: Blob không triển khai area()
abstract class Polygon(name: String) : Shape(name)   // hợp lệ: vẫn là lớp trừu tượng

Lớp trừu tượng có thể có hàm khởi tạo, trạng thái và các phương thức thông thường mà các lớp con kế thừa. Một cách dùng phổ biến là quy trình cố định có các bước thay đổi, gọi là phương thức mẫu: phương thức của lớp cha chạy các bước, còn các lớp con hoàn thiện những bước trừu tượng:

abstract class Report(val title: String) {
    fun render() = "== $title ==\n${body()}\n(${footer()})"

    abstract fun body(): String
    open fun footer() = "end"
}
class Sales(val total: Int) : Report("Sales") {
    override fun body() = "total: $total"
}
class Stock(val items: Int) : Report("Stock") {
    override fun body() = "items: $items"
    override fun footer() = "counted today"
}

Bên trong main:

println(Sales(420).render())
println(Stock(17).render())

Đầu ra:

== Sales ==
total: 420
(end)
== Stock ==
items: 17
(counted today)

Thuộc tính trừu tượng hoạt động theo cách tương tự. Lớp con cung cấp giá trị dưới dạng thuộc tính của hàm khởi tạo, thuộc tính được lưu trữ hoặc getter:

abstract class Worker(val name: String) {
    abstract val monthly: Int
    fun yearly() = monthly * 12
}
class Salaried(name: String, override val monthly: Int) : Worker(name)
class Hourly(name: String, val hours: Int, val rate: Int) : Worker(name) {
    override val monthly: Int
        get() = hours * rate
}

Bên trong main:

val staff = listOf(Salaried("Ada", 4000), Hourly("Bo", 80, 25))
for (w in staff) println("${w.name}: ${w.yearly()}")

Đầu ra:

Ada: 48000
Bo: 24000
challenge icon

Thử thách

Dễ

Discount là một lớp trừu tượng. Lớp này khai báo phương thức trừu tượng amount(price) và triển khai apply(price), phương thức này trả về giá trừ đi amount(price) nhưng không bao giờ thấp hơn 0. Hãy hoàn thành các lớp con: PercentOff(percent) giảm giá theo tỷ lệ phần trăm đó trên giá (làm tròn xuống), FixedOff(value) giảm một giá trị cố định, còn SpendMore(threshold, off) chỉ giảm off khi giá ít nhất bằng threshold.

Code được cung cấp đọc giá, sau đó đọc các mức giảm giá p,10, f,300 hoặc s,500,50, áp dụng từng mức giảm giá vào giá và in ra 1000 -> 900.

Code của bạn nằm trong Discount.kt, PercentOff.kt, FixedOff.kt và SpendMore.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 price = input[0].toInt()
    for (line in input.drop(1)) {
        val p = line.split(",")
        val discount: Discount = when (p[0]) {
            "p" -> PercentOff(p[1].toInt())
            "f" -> FixedOff(p[1].toInt())
            else -> SpendMore(p[1].toInt(), p[2].toInt())
        }
        println("$price -> ${discount.apply(price)}")
    }
}
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