iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0

昨日我們已經介紹 Kotlin 類別的基本使用方式,接下來我們來談繼承介面抽象的使用方法,在 Kotlin 中,我們要使用繼承時,會有以下三件事要注意:

  1. 需要使用 操作符號
  2. 被繼承的類別必須在 class 前面加上 open 關鍵字
  3. 若父類別的主建構函數(Primary Constructor)有參數,必須在繼承時帶入資料

我們利用上面三點事項撰寫下面範例:

fun main() {
	// 呼叫 Author 類別
    val author = Author("Devin")
    
    // 印出 Devin
    println(author.name)
}

// 加上 open 關鍵字代表此類別可被繼承
open class Person(name: String){
    val name: String = name
}

// 建立一個 Author 類別繼承 Person 類別
class Author(name: String) : Person(name)

而當子類別繼承後,如果子類別要使用父類別的函數,我們就要使用到 super 關鍵字進行呼叫,如下範例:

fun main() {
    val man1 = SuperMan("Devin")
    val man2 = SuperMan("Eric", "Eric@Eric.com")

    println(man1.name)    // 印出 Devin
    println(man1.email)   // 印出 ""
    println(man2.name)    // 印出 Eric
    println(man2.email)   // 印出 "Eric@Eric.com"
}

open class Person(val name: String) {
    var email: String = ""

    constructor(name: String, email: String) : this(name) {
        this.email = email
    }
}

class SuperMan : Person {
    constructor(name: String) : super(name)
    constructor(name: String, email: String) : super(name, email)
}

當子類別繼承父類別後,若子類別想要覆寫函數可使用 override 關鍵字,記得繼承的函數也要使用 open 關鍵字進行宣告,如下範例:

fun main() {
    val man = SuperMan("Devin")
    man.sayHello()
}

open class Person(val name: String) {
    var email: String = ""

    constructor(name: String, email: String) : this(name) {
        this.email = email
    }

    open fun sayHello() {
        println("Hi, 我是Person")
    }
}

class SuperMan : Person {
    constructor(name: String) : super(name)
    constructor(name: String, email: String) : super(name, email)

		// 覆寫方法
    override fun sayHello() {
        println("Hi, 我是SuperMan")
    }
}

在 Kotlin 中使用繼承時,要注意程式執行先後順序,我們會直接利用範例搭配下面步驟逐步觀察:

  1. 程式會先執行 SuperMan 類別主要建構函數的 println 方法
  2. 再進入父類別 Person ,執行該類別的 init 區塊程式
  3. 再執行父類別 Person的次建構函數
  4. 回到子類別,執行該類別的 init 區塊程式
  5. 再因 main 函數呼叫 sayHello 方法,藉由 super 關鍵字呼叫父類別的 sayHello 函數
  6. 最後才會執行子類別的 sayHello println 函數
fun main() {
    val man = SuperMan("Devin")
    man.sayHello()
}

open class Person(open val name: String) {
    var email: String = ""
	
	// 執行步驟 2
    init {
        println("Person init 區塊")
    }

	// 執行步驟 3
    constructor(name: String, email: String) : this(name) {
        println("Person Name: $name")
        this.email = email
    }

	// 執行步驟 5
    open fun sayHello() {
        println("Hi, 我是Person")
    }
}

// 執行步驟 1
class SuperMan(override val name: String) : Person(name, email = "Test".also { println("帶入 Email 資料") }) {

	// 執行步驟 4
    init {
        println("SuperMan init 區塊")
    }

	// 執行步驟 6
    override fun sayHello() {
				super.sayHello()
        println("Hi, 我是SuperMan")
    }
}

在繼承特性中,我們可以使用 var 定義的變數覆寫(override) val 父類別屬性,但要記得我們無法使用 val 覆寫(override) var 屬性,如下範例:

fun main() {
    SuperMan("devin")
}

// 定義可繼承的 Person 類別與 val name 屬性 
open class Person(open val name: String)

// override 父類別屬性,將 val 改為 var
class SuperMan(private var _name: String): Person(_name) {
    override var name: String = _name
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }

    init {
        println(name)
    }
}

介面 Interface

Kotlin 與 Java 一樣,只能繼承一個類別,但可以實作多個介面,而介面實作也是使用 操作符號進行實現,而 Kotlin 與 Java 不同的地方是 Kotlin 的 Interface 可以自己實作函數,而使用介面的好處主要是為了解決耦合問題(Coupling)與支援多重繼承功能,例如以下範例:

fun main() {
    val myClass = MyClass()
    myClass.sayHello()
    myClass.printData()
}

interface Interface1 {
    fun printData()

	// 介面本身自已實作
    fun haveImplement() {
        println("Kotlin 介面可自己實作,而且類別不需要實作")
    }
}

interface Interface2 {
    fun sayHello()
}

// 繼承多重介面
class MyClass : Interface1, Interface2 {
    override fun printData() {
		// 呼叫介面已實作函數
        haveImplement()
    }

    override fun sayHello() {
        println("Hi")
    }
}

當介面方法相同時,我們可以使用 super 關鍵字進行呼叫特定介面的方法,如下範例:

fun main() {
    MyClass().haveImplement()
}

interface Interface1 {
    fun haveImplement() {
        println("Interface1 實作")
    }
}

interface Interface2 {
    fun haveImplement() {
        println("Interface2 實作")
    }
}

// 繼承多重介面
class MyClass : Interface1, Interface2 {
    override fun haveImplement() {
		// 利用 super 呼叫指定介面的函數
        super<Interface2>.haveImplement()
    }
}

前面提到的耦合(Coupling)其實就是指兩個模組之間的相依性,若相依性越高,則耦合度越高,即為高耦合問題,耦合性越高的話,容易因為小需求變動而連貫影響整個系統或其他模組,例如以下範例,類別 A 與 類別 B 存在直接相依性的問題:

class A {
	val message: String
}

class B {
	fun sayHello(a: A) {
		println(a.message)
	}
}

實現低耦合就是對兩類別之間進行解耦,解除類別之間的直接關係,將直接關係轉換成間接關係:

  1. 將類別共用方法抽離成 Interface,再直接使用 override 方法執行

    fun main() {
        Boy().sayHello()
        Girl().sayHello()
        Woman().sayHello()
    }
    
    interface Person {
        fun sayHello(): Unit
    }
    
    class Boy : Person {
        override fun sayHello() {
            println("Hello, Boy")
        }
    }
    
    class Girl : Person {
        override fun sayHello() {
            println("Hello, Girl")
        }
    }
    
    class Woman : Person {
        override fun sayHello() {
            println("Hello, Woman")
        }
    }
    
  2. 利用依賴注入(Dependency Injection, DI)方法達到類別彼此間的間接關係,即我們是將被依賴物件注入被動接收物件當中,以下面範例為例:

    fun main() {
        // 將被依賴物件注入被動接收物件
        // 若學生有學習新的語言,只要新增一個類別再丟入 MyStudent 即可
        MyStudent(English()).study()
        MyStudent(Chinese()).study()
    }
    
    // 建立學生類別
    class MyStudent(private val language: Language) {
        // 正在讀書
        fun study() {
            language.speak()
        }
    }
    
    // 建立邏輯共用介面-語言
    interface Language {
        fun speak();
    }
    
    // 當需要 English 時,建立類別
    class English : Language {
        override fun speak() {
            println("學生正在練習英文口說")
        }
    }
    
    // 當需要 Chinese 時,建立類別
    class Chinese : Language {
        override fun speak() {
            println("學生正在練習中文口說")
        }
    }
    

抽象類別 abstract class

在 Kotlin 中抽象類別會使用到 abstract 關鍵字,必須加在 class 或 function 前面,而抽象類別無法像普通類別一樣被實例化(Instance),它只能被類別繼承,而抽象類別也能使用建構函數進行外部參數引入,如下範例:

fun main() {
    SuperMan("Devin", "").hello()
}

// 定義抽象類別
abstract class Person (val name: String, val email: String) {
    abstract fun hello()
}

// 定義
class SuperMan(name: String, email: String) : Person(name, email) {
    override fun hello() {
		// 印出「我是 Devin」 
        println("我是 $name ") 
    }
}

抽象類別與介面主要差別還是在於使用場景或身份的不同,因類別只能單一繼承,所以使用抽象類別的子類別幾乎都是會有高關聯的,但介面不見得,我們可以依需求來選擇合適的介面實作,建議大家還是要依照需求來選擇合適方式。


上一篇
[Day 07] 遠征 Kotlin × 類別與物件
下一篇
[Day 09] 遠征 Kotlin × 例外處理
系列文
30天從零撰寫 Kotlin 語言並應用於 Spring Boot 開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言