[Day 08] 遠征 Kotlin × 類別繼承、介面、抽象
昨日我們已經介紹 Kotlin 類別的基本使用方式,接下來我們來談繼承
、介面
與抽象
的使用方法,在 Kotlin 中,我們要使用繼承
時,會有以下三件事要注意:
- 需要使用
:
操作符號
- 被繼承的類別必須在
class
前面加上 open
關鍵字
- 若父類別的主建構函數(Primary Constructor)有參數,必須在繼承時帶入資料
我們利用上面三點事項撰寫下面範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fun main() { val author = Author("Devin")
println(author.name) }
open class Person(name: String){ val name: String = name }
class Author(name: String) : Person(name)
|
而當子類別
繼承後,如果子類別要使用父類別的函數,我們就要使用到 super
關鍵字進行呼叫,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| fun main() { val man1 = SuperMan("Devin") val man2 = SuperMan("Eric", "Eric@Eric.com")
println(man1.name) println(man1.email) println(man2.name) println(man2.email) }
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
關鍵字進行宣告,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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 中使用繼承時,要注意程式執行先後順序
,我們會直接利用範例搭配下面步驟逐步觀察:
- 程式會先執行 SuperMan 類別主要建構函數的 println 方法
- 再進入父類別 Person ,執行該類別的 init 區塊程式
- 再執行父類別 Person的次建構函數
- 回到子類別,執行該類別的 init 區塊程式
- 再因 main 函數呼叫 sayHello 方法,藉由 super 關鍵字呼叫父類別的 sayHello 函數
- 最後才會執行子類別的 sayHello println 函數
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| fun main() { val man = SuperMan("Devin") man.sayHello() }
open class Person(open val name: String) { var email: String = ""
init { println("Person init 區塊") }
constructor(name: String, email: String) : this(name) { println("Person Name: $name") this.email = email }
open fun sayHello() { println("Hi, 我是Person") } }
class SuperMan(override val name: String) : Person(name, email = "Test".also { println("帶入 Email 資料") }) {
init { println("SuperMan init 區塊") }
override fun sayHello() { super.sayHello() println("Hi, 我是SuperMan") } }
|
在繼承特性中,我們可以使用 var
定義的變數覆寫(override) val
父類別屬性,但要記得我們無法使用 val
覆寫(override) var
屬性,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| fun main() { SuperMan("devin") }
open class Person(open val name: String)
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)與支援多重繼承功能,例如以下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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 關鍵字進行呼叫特定介面的方法,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| fun main() { MyClass().haveImplement() }
interface Interface1 { fun haveImplement() { println("Interface1 實作") } }
interface Interface2 { fun haveImplement() { println("Interface2 實作") } }
class MyClass : Interface1, Interface2 { override fun haveImplement() { super<Interface2>.haveImplement() } }
|
前面提到的耦合(Coupling)其實就是指兩個模組之間的相依性,若相依性越高,則耦合度越高,即為高耦合問題,耦合性越高的話,容易因為小需求變動而連貫影響整個系統或其他模組,例如以下範例,類別 A 與 類別 B 存在直接相依性的問題:
1 2 3 4 5 6 7 8 9
| class A { val message: String }
class B { fun sayHello(a: A) { println(a.message) } }
|
實現低耦合就是對兩類別之間進行解耦,解除類別之間的直接關係,將直接關係轉換成間接關係:
將類別共用方法抽離成 Interface,再直接使用 override 方法執行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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") } }
|
利用依賴注入(Dependency Injection, DI)方法達到類別彼此間的間接關係,即我們是將被依賴物件注入被動接收物件當中,以下面範例為例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| fun main() { MyStudent(English()).study() MyStudent(Chinese()).study() }
class MyStudent(private val language: Language) { fun study() { language.speak() } }
interface Language { fun speak(); }
class English : Language { override fun speak() { println("學生正在練習英文口說") } }
class Chinese : Language { override fun speak() { println("學生正在練習中文口說") } }
|
抽象類別 abstract class
在 Kotlin 中抽象類別會使用到 abstract 關鍵字,必須加在 class 或 function 前面,而抽象類別無法像普通類別一樣被實例化(Instance),它只能被類別繼承,而抽象類別也能使用建構函數進行外部參數引入,如下範例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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() { println("我是 $name ") } }
|
抽象類別與介面主要差別還是在於使用場景或身份的不同,因類別只能單一繼承,所以使用抽象類別的子類別幾乎都是會有高關聯的,但介面不見得,我們可以依需求來選擇合適的介面實作,建議大家還是要依照需求來選擇合適方式。