Menu
Coddy logo textTech

抽象クラス

CoddyのKotlinジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 20/57。

abstract クラスは、それ自体ではインスタンス化できないスーパークラスです。ボディなしで 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 キーワードなしでオープンであり、その抽象メンバーもオープンなので、サブクラスでは 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)

抽象プロパティも同じように機能します。サブクラスは、コンストラクターのプロパティ、格納プロパティ、またはゲッターとして値を提供します。

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) を宣言し、価格から amount(price) を差し引いた値を返す apply(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プレイグラウンド