Kotlin Tutorial(12)繼承與函式覆寫 by Michael | CodeData
top

Kotlin Tutorial(12)繼承與函式覆寫

分享:

Kotlin Tutorial(11)類別與物件 << 前情

如果應用程式的功能比較多一些,或是架構比較複雜一些,開發人員通常會使用物件導向的繼承,可以使用更精簡的程式碼,完成應用程式需要的功能。開發Android App元件的時候,也會繼承Android SDK提供的類別,再完成元件需要的工作。所以瞭解繼承與函式覆寫,幾乎是開發各種應用程式必要的特性。

沒有使用繼承設計的類別

設計瀏覽與管理記事資料的App,提供使用者儲存基本的標題與內容:

kt_11_01

為了讓App可以提供更多功能,讓使用者可以另外儲存照片的記事資料:

kt_12_01

依照基本的封裝特性,你會宣告一個像這樣的基本記事類別:

/* net.macdidi5.kotlin.tutorial.ch12.stage01.Item.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage01

// 宣告封裝項目的類別
//   包含編號、標題與內容三個屬性
class Item (val id: Long,
            var title: String,
            var content: String)

可以儲存照片的記事類別,依照同樣的封裝概念,你會宣告這樣的類別:

/* net.macdidi5.kotlin.tutorial.ch12.stage01.ImageItem.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage01

// 宣告封裝可以儲存照片項目的類別
//   包含編號、標題、內容與照片檔名四個屬性
class ImageItem(val id: Long,
                var title: String,
                var content: String,
                var imageFile: String)

依照使用者的選擇,在App運作的時候,使用上面宣告的類別建立需要的物件:

/* net.macdidi5.kotlin.tutorial.ch12.stage01.Demo01.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage01

fun main(args: Array<String>) {
    val i = Item(1, "Hello", "Hello Kotlin!")
    println("id=${i.id}, title=${i.title}, content=${i.content}")

    val i02 = ImageItem(2, "Hi", "Hello", "kotlin.jpg")
    println("id=${i02.id}, title=${i02.title}, " +
            "content=${i02.content}, imageFile=${i02.imageFile}")
}

使用繼承設計的類別

你可以發現上面的作法是很不好的,一般記事類別Item與可以儲存照片的ImageItem類別,有重複的屬性宣告,如果需要變更的時候,例如為記事資料加入日期與時間,那所有類別都需要修改。

所以在設計應用程式的時候,為了避免重複的宣告,應該使用繼承的特性來設計這些類別。應用程式已經宣告好一般記事類別Item,在宣告可以儲存照片的ImageItem類別之前,先把Item類別定義為可以被繼承的父類別(super class),宣告類別的時候沒有使用open關鍵字,表示不可以被繼承:

/* net.macdidi5.kotlin.tutorial.ch12.stage02.Item.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage02

// 在宣告項目類別的時候,使用open關鍵字讓它可以被繼承
open class Item (val id: Long,
                 var title: String,
                 var content: String)

下面是宣告子類別的基本語法:

class 類別名稱(參數名稱: 參數型態,..., val|var 屬性名稱: 屬性型態): 父類別名稱(參數,...)

接下來在宣告可以儲存照片的ImageItem類別,使用上面的語法讓它繼承Item類別:

/* net.macdidi5.kotlin.tutorial.ch12.stage02.ImageItem.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage02

// 在宣告主要建構式的時候
//   從父類別繼承的id、title與content屬性
//   不使用val或var宣告,所以它們只是參數
//   另外使用var宣告ImageItem需要的imageFile屬性
// 在類別宣告的最後面
//   使用Item(id, title, content)呼叫父類別Item的主要建構式
//   設定編號、標題與內容
class ImageItem(id: Long,
                title: String,
                content: String,
                var imageFile: String): Item(id, title, content)

使用繼承設計的類別也可以依照使用者的選擇,在App運作的時候建立需要的物件:

/* net.macdidi5.kotlin.tutorial.ch12.stage02.Demo02.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage02

fun main(args: Array<String>) {
    val i = Item(1, "Hello", "Hello Kotlin!")
    println("id=${i.id}, title=${i.title}, content=${i.content}")

    // 建立ImageItem物件後,包含從父類別Item繼承的id、title與content
    val i02 = ImageItem(2, "Hi", "Hello", "kotlin.jpg")
    println("id=${i02.id}, title=${i02.title}, " +
            "content=${i02.content}, imageFile=${i02.imageFile}")
}

函式覆寫

父類別宣告的屬性與函式,都會被子類別繼承,子類別繼承自父類別的函式,會依照函式的工作,可能需要在子類別執行覆寫(Overriding Methods)的工作。在宣告父類別函式的時候,加入open關鍵字讓子類別可以覆寫這個函式:

/* net.macdidi5.kotlin.tutorial.ch12.stage03.Item.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage03

open class Item (val id: Long,
                 var title: String,
                 var content: String) {

    // 傳回簡短的記事內容
    // 因為不需要讓子類別覆寫,所有不需要加入open關鍵字
    fun getReduceContent(length: Int = 5) =
            "${content.substring(0 until length)}..."

    // 加入open關鍵字讓子類別可以覆寫這個函式
    // 因為子類別需要加入額外的資訊,例如ImageItem的imgaeFile
    open fun getDetails() = "id=$id, title=$title, content=$content"
}

宣告子類別的時候,加入override關鍵字覆寫父類別的函式:

/* net.macdidi5.kotlin.tutorial.ch12.stage03.ImageItem.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage03

class ImageItem(id: Long,
                title: String,
                content: String,
                var imageFile: String): Item(id, title, content){

    // 不用覆寫繼承自父類別的getReduceContent函式

    // 加入override關鍵字覆寫父類別的函式
    //   顯示繼承自父類別的id、title與content
    //   還有自己宣告的imageFile
    override fun getDetails() = "id=$id, title=$title, " +
            "content=$content, imageFile=$imageFile"
}

ImageItem子類別不用宣告父類別已經宣告好的函式,只要針對需要覆寫的函式實作就可以了:

/* net.macdidi5.kotlin.tutorial.ch12.stage03.Demo03.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage03

fun main(args: Array<String>) {
    val i = Item(1, "Hello", "Hello Kotlin!")
    // 顯示呼叫函式傳回的項目資訊
    println(i.getDetails())
    // 顯示簡短的記事內容(預設為五個字)
    println(i.getReduceContent())

    val i02 = ImageItem(2, "Hi", "Good morning", "kotlin.jpg")
    // 顯示呼叫函式傳回的項目資訊,這是ImageItem自己寫的函式
    println(i02.getDetails())
    // 顯示四個字的簡短記事內容,這是繼承自父類別Item的函式
    println(i02.getReduceContent(4))
}

在覆寫的函式中呼叫父類別的函式

比對上面實作的程式碼,應該很容易發現不太好的作法:

// Item類別的函式
open fun getDetails() = "id=$id, title=$title, content=$content"

// ImageItem類別的函式,包含父類別getDetails函式的程式碼
override fun getDetails() = "id=$id, title=$title, " +
        "content=$content, imageFile=$imageFile"    

這樣的作法就違反繼承重要的特性:避免重複的程式碼。所以在子類別覆寫函式的時候,通常會使用super關鍵字呼叫父類別的函式:

/* net.macdidi5.kotlin.tutorial.ch12.stage04.ImageItem.kt */

package net.macdidi5.kotlin.tutorial.ch12.stage04

class ImageItem(id: Long,
                title: String,
                content: String,
                var imageFile: String): Item(id, title, content){

    // 使用super關鍵字呼叫父類別的函式
    override fun getDetails() = "${super.getDetails()}, imageFile=$imageFile"
}

下一步

瞭解繼承基本的特性與作法以後,接下來準備認識物件導向基礎中的最後一個多型的特性,不論是自己設計應用程式,或是使用各種架構的API,都需要瞭解多型的特性,才可以使用精簡的程式碼完成應用程式的設計。

相關的檔案都可以在GitHub瀏覽與下載。

https://github.com/macdidi5/Kotlin-Tutorial

後續 >> Kotlin Tutorial(13)多型的特性與應用

分享:
按讚!加入 CodeData Facebook 粉絲群

相關文章

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言