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.0Lớ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ượngLớ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: 24000Thử 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)}")
}
}
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
1Lớp và đối tượng
Làm việc với tệpĐịnh nghĩa lớpHàm khởi tạo chínhKhối init và kiểm tra tính hợp lệHàm khởi tạo phụÔn tập: thẻ thư viện4Kế thừa
Lớp mở và lớp conGhi đè thành viênsuper và hàm khởi tạoLớp trừu tượngÔn tập: Danh bạ nhân viên2Thuộc tính và tính đóng gói
Trình truy cập tùy chỉnhBộ sửa đổi phạm vi truy cậpSetter riêng tưlazy và lateinitÔn tập: tài khoản ngân hàng5Giao diện
Khai báo giao diệnPhương thức mặc địnhNhiều giao diệnGiao diện như kiểu dữ liệuÔn tập: Phương thức thanh toánTự luyện tập: Playground Kotlin