iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0
Mobile Development

新手向Android&Kotlin學習紀錄30天系列 第 27

第27天 Kotlin小學堂(16) : 抽象類別與介面

  • 分享至 

  • xImage
  •  

Abstract 抽象

有些概念但細節不明確,我們不希望也不能直接實作出來,那我們就可以依此抽象概念先使用抽象類別列出抽象屬性及抽象函數,讓繼承他的子類別去實作出來。
依照之前提過類別像藍圖的概念來說,那抽象類別就像草圖,繼承抽象類別的子類別像藍圖,最後依藍圖實例化出實例。

定義抽象類別

  • 使用abstract修飾符加在類別、屬性、函數的前面
  • 因為細節不夠所以無法直接實例化,需透過子類別實現
  • 不需要聲明open就可以被繼承,因為這本來就是abstract的用意
  • abstract類別內可以有抽象屬性、抽象函數
  • 抽象屬性、抽象函數只能在抽象類別裡,並且不可放在建構函數中
  • 抽象屬性不需要初始值、抽象函數不需要本體{ },如下例:
    abstract class SomeClass() {
        abstract val foo: String
        abstract fun bar(): String
    }
    
  • 舉例:
//抽象父類別
abstract class House(val id: String, val room: Int, var price: Int) {
    //一個抽象屬性
    abstract val type: String
    //兩個抽象函數:沒有函數本體
    abstract fun getBasicInfo(): String
    abstract fun getExtraInfo(): String
    //一般函數:印出所有訊息
    fun getAllInfo() {
        println(getBasicInfo() + "\n" + getExtraInfo())
    }
}
//子類別
class Apartment(id: String, room: Int, price: Int, private val floor: Int, private var houseAge: Int) :
    House(id, room, price) {
     //實作抽象屬性   
    override val type: String = "Apartment"
    //實作兩個抽象類別   
    override fun getBasicInfo(): String {
        return "id: $id , type: $type , room: $room ,price: $price"
    }
    override fun getExtraInfo(): String {
        return " floor: $floor , houseAge: $houseAge "
    }
}
    
fun main() {  
    //實作Apartment
    val apartment = Apartment("ap0001", 4, 500, 5, 22)
    //直接呼叫非抽象的函數
    apartment.getAllInfo()
    //結果:
//    id: ap0001 , type: Apartment , room: 4 ,price: 500 
//    floor: 5 , houseAge: 22 
}      

interface 介面

介面的用途就是先約定一些屬性和行為在類別之間共用,而共用就是介面的目的。
介面只是定義用途,不管怎麼實作。所以它沒有建構函數這個初始化工具,裡面的定義的函數也沒有函數本體。

  • 使用 interface關鍵字定義介面
  • Kotlin中介面主要的成員是抽象屬性及抽象函數
  • 也可以定義一般的屬性(透過getter)及函數
  • 介面中的屬性和函數都預設是open
  • 可見性修飾符會是預設的public
  • 介面與抽象類別一樣不能直接使用,必須透過類別實作
  • 介面與抽象類別不一樣是,介面無法保存狀態。
  • 一個類別只能繼承一個父類別,但可以實作多個介面。

定義一個介面

直接看例子:

  1. 定義一個Usb介面
interface Usb {
    fun enable() //抽象函數,前面省略了public abstract
    fun close()
    val deriveName: String  //一般的屬性,介面中宣告屬性只能透過getter
        get() = "USB"
    fun printDeriveName(){  //一般函數
        println(deriveName)
    }
}
  1. Mouse類別和Keyboard實作Usb介面,
  • 宣告實作的方式跟繼承一樣使用:,如果還有父類別的話以逗號隔開
  • 雖然是實作而不是覆寫的意思,但Kotlin規定依然使用override當作實作抽象屬性及函數的關鍵字`
class Mouse : Usb {
    override val deriveName: String  //可選要不要覆寫
        get() = "mouse"
    //兩個抽象函數必須實作
    override fun enable() {         
        println("Mouse inserted")
    }
    override fun close() {
        println("Unplugged the Mouse")
    }
}

class Keyboard : Usb {
    //兩個抽象函數必須實作
    override fun enable() {
        println("Keyboard inserted")
    }
    override fun close() {
        println("Unplugged the Keyboard")
    }
    override val deriveName: String     //可選要不要覆寫
        get() = "Keyboard"

}
  1. 定義Computer類別
class Computer {
    fun bootUp() {
        println("Computer boot up")
    }

    fun shutDown() {
        println("Computer shut down")
    }
    //使用Usb連結Mouse、Keyboard等設備
    fun useEquipment(usb: Usb) {
        usb.printDeriveName()
        usb.enable()
        usb.close()
    }
}
  1. 實例化電腦及設備,並使用他們
fun main() {
    val computer = Computer()
    val mouse = Mouse()
    val keyboard = Keyboard()

    computer.apply {
        bootUp()
        useEquipment(mouse)
        useEquipment(keyboard)
        shutDown()
    }
  /* 結果 :
    Computer boot up
    deriveName : mouse
    Mouse inserted
    Unplugged the Mouse
    deriveName : Keyboard
    Keyboard inserted
    Unplugged the Keyboard
    Computer shut down     */
}

介面繼承介面

interface Name {
    val name: String
}
//Person介面繼承Name介面,並實作抽象屬性name
interface Person : Name {
    val firstname: String
    val lastname: String
    override val name: String
        get() = "$firstname $lastname"
}

//實作Person介面,實作抽象屬性成為主建構函數的參數。
//因為name已被Person實作過,所以不一定要覆寫它
class Student(
    override val firstname: String,
    override val lastname: String,
    private val studentId: Int
) : Person

多介面的同名函數衝突

  1. 不同介面擁有同名函數 : foo()、bar()
interface A {
    fun foo(){
        print("A")  
    }
    fun bar()   //抽象函數
}
interface B{
    fun foo(){
        print("B")
    }
    fun bar(){
        println("bar")
    }
}
  1. C 實作A介面,實現抽象函數bar();
  2. D 實作A和B介面,所以必須明確說明如何實現這A、B介面的同名函數,以super<A>.foo()這種語法在尖括號中指定介面。(發生衝突的函數必須是已完成的函數,這樣主程式才知道怎麼執行)
    因為A介面的bar()已被C類別實現過所以不必實現
class C:A{
    override fun bar() {
        println("A bar")
    }
}

class D :A,B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }
    override fun bar() {
        super<B>.bar()
    }
}

抽象類別 vs. 介面 都幾?

兩者該如何選擇使用呢? 好像不能因為是大人所以同時全都要

針對如何使用,有個不錯的經驗法則:
在需要一組公共物件行為或屬性時,如果繼承行不通就改用介面。
另一方面,如果繼承可行,但又不太需要太具體的父類別時,便採用抽象類別
(如果還想產生父類別的實例就只能使用一般類別)
節錄自 Kotlin權威2.0

明天見


上一篇
第26天 Kotlin小學堂(15) :標準函數
下一篇
第28天 認識Activity、Toast、Snackbar
系列文
新手向Android&Kotlin學習紀錄30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言