Menu
CoddyTech

抽象类

本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 20 课,共 57 课。

抽象类是一种超类,不能单独实例化。它可以声明没有函数体的abstract成员,每个具体子类都必须实现这些成员:

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
}

在main中:

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

输出:

circle with area 3.14
rect with area 6.0

抽象类默认是 open 的,不需要写关键字;它的抽象成员也默认是 open 的,所以子类只需编写 override。抽象类本身无法编译,而省略了抽象成员的子类也必须是抽象类。当超类的某些成员没有合理的行为实现时,就使用抽象类:

// val s = Shape("blob")                       // 错误:无法创建抽象类的实例
// class Blob : Shape("blob")                  // 错误:Blob 没有实现 area()
abstract class Polygon(name: String) : Shape(name)   // 没问题:仍然是抽象类

抽象类可以有构造函数、状态和普通方法,这些都会被子类继承。一种常见用途是定义一个步骤固定但具体步骤有所不同的流程,即模板方法:超类方法负责执行这些步骤,子类则实现抽象方法:

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

在 main 中:

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

输出:

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

抽象属性的工作方式也一样。子类通过构造函数属性、存储属性或 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
}

在 main 中:

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

输出:

Ada: 48000
Bo: 24000
challenge icon

挑战

简单

Discount 是一个抽象类。它声明了抽象方法 amount(price),并实现了 apply(price),该方法返回价格减去 amount(price) 的结果,但不会低于 0。请完成这些子类:PercentOff(percent) 按价格的指定百分比打折(向下取整),FixedOff(value) 减去固定金额,SpendMore(threshold, off) 仅当价格至少为 threshold 时才减去 off。

提供的代码会读取价格,然后读取折扣 p,10、f,300 或 s,500,50,将每项折扣应用到价格上,并输出 1000 -> 900。

你的代码应写在 Discount.kt、PercentOff.kt、FixedOff.kt 和 SpendMore.kt 中。Main.kt 包含提供的输入/输出代码,不能编辑。

亲手试试

fun main() {
    // 提供的输入输出代码:保持原样。
    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 icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Kotlin 在线编辑器