iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 9
0
Mobile Development

30天,從0開始用Kotlin寫APP系列 第 9

Day 09 | Kotlin 的物件導向程式設計(Object-oriented programming, OOP)- Part 1

相信有寫過 Java 的捧友應該對 OOP 不陌生,即使寫不出很漂亮的 OOP 架構(就是在說我...慚愧),至少也看過或聽過繼承、封裝、多型、抽象、以及還有很多很多的設計模式,這邊有一張圖可以大概的表示 OOP 知識框架

那 Kotlin 作為 Java 的後繼者,許多地方都與 Java 雷同,因此實作 OOP 也是沒有問題的,因此今天就要來了解一下 Kotlin 在實作 OOP 上和 Java 有哪些差異以及需要注意哪些問題

類別( Class )

Kotlin 和多數語言一樣採用 class 定義類別

class doSomeThing {
    //  建構子、邏輯、判斷...
}

Kotlin 類別成員( Class Member)

Kotlin 中的類別成員有

  • 建構子( Constructors ) and 初始化區塊( initializer blocks )
  • 屬性( Properties )
  • 函式( Functions )
  • 巢狀類別( Nested Classes ) and 嵌入式類別( Inner Classes )
  • 物件聲明( Object Declarations )

建構子( Constructor )

Java 的 Class 中,Constructor 會長的像這樣

public class doSomeThing {
    String s;
    public doSomThing(String s) {
        this.s = s;
    }
}

但在 Kotlin 中, Class 的 Constructor 分成兩種

  • 主建構子( Primary Constructor )
  • 次建構子( Secondary Constructor )

主建構子( Primary Constructor )

Primary Constructor 在 Class 中只會有一個,並會放在 class 後面,當作 class header 的一部份

// 括號中就是 Primary Constructor 區塊
class Person(var firstName: String, var lastName: String, var age: Int) { 
    //  建構子、邏輯、判斷...
}

在 Primary Constructor 區塊中不能寫邏輯,如果需要做初始化邏輯的部份可以交給 initializer blocks ,或是可以用來初始化宣告出來的變數

class Person(var firstName: String, var lastName: String, var age: Int) { 
    // 宣告變數的初始值是 Primary constructor 的 s
    var firstName: String = firstName.toUpperCase()
    
    // initializer blocks, 在 Constructor 後,第一個會被執行的區塊
    init {
        println("Primary constructor: ${age}")
    }
}

次建構子( Secondary Constructor )

Secondary ConstructorPrimary Constructor 的差別是

Primary Constructor 在一個 Class 中只能有一個
Secondary Constructor 在一個 Class 中可以有多個
[color=orange]

Secondary Constructor 的寫法就會比較近似 Java 定義 Constructor 的方式,只是 Java 中使用的 Class name 換成 constructor

Example:

class Dog {
    var host: mutableList<Person> = mutableListOf()
    constructor(host: Person) {
        this.host.add(host)
    }
}

而 Primary 和 Secondary constructor 可以同時存在於 Class 中
但這時就要注意,如果已經存在 Primary constructor ,那之後創造的每個 Secondary constructor 都必須 Delegate Primary constructor

Example:

class Dog(val name: String) {
    var host: mutableList<Person> = mutableListOf()
    constructor(name: String, host: Person): this(name) {
        this.host.add(host)
    }
}

那上面有提到 Class 中可以撰寫 initializer blocks, 那麼這邊要注意在程式的實行順序會是依照:

  1. Primary Constructor
  2. initializer blocks
  3. Secondary Constructor

因此在使用 Constructor 時,需要注意以上的狀況喔

屬性( Properties )

在建立 Class 後可能會需要建立 Properties ,那在 Properties 可以分成 varval,那兩者個差別在前幾天變量介紹的時候就已經有探討過了,這邊快速複習一下

val : Read-only ,只能讀不能寫
var : Read and write, 可讀可寫
[color=orange]

那要定義成 varval 就會根據需求而定,下面給了一個 Dog class 的範例,可以注意到

  1. name 寫在 Primary constructor 裏面,因為我希望被實作時要先給狗狗的名稱,並且不能再重新給狗狗名稱,因為在我的世界裡是不允許棄養狗狗的!!!
  2. age 的變量是 var ,型態是 Int ,預設是 0
  3. host 的變量是 var ,型態是 List ,並且 List 中只能存 String
  4. legs 的變量是 val ,型態是 Int ,並且預設值是4 ,因為狗狗有四條腿
// Dog.kt
class Dog(val name: String) {
    var age: Int = 0
    var host: MutableList<Person> = mutableListOf()
    val legs: Int = 4
}

那在實作一隻叫 Bob 的狗時,首先我要先去實作 Dog class ,並給他 Bob 當作 Dog name

fun createDog() {
    val mBob: Dog = Dog("Bob")
        println(mBob.name)  // Output: Bob
        mBob.name = "Alice" // 這是不被允許的,因為 'name' 是 'val'
        
        println(mBob.age)  // Output: 0
        mBob.age = 3
        println(mBob.age)  // Output: 3
        
        println(mBob.host)  // Output: []
        mBob.host.add(Alice)
        mBob.host.add(Charlie)
        println(mBob.host.size)  // Output: 2
        
}

另外,從這個例子還可以注意到,在使用 Properties 時並不用像 Java 需要寫 getter()setter() ,因為 Kotlin 中的 Properties 會自帶 getter()setter() ,因此這邊回顧上面 var 和 val ,就可以再補充一點

val : Read-only ,只能讀不能寫,因為只有 getter()
var : Read and write, 可讀可寫,因為有 getter()setter()
[color=orange]

函式( Function )

在 Class 中當然也可以寫 function 去增強 Class 的能力, Kotlin 的用法和 Java 的用法一樣,這邊直接給個範例,繼續給剛剛的 Dog class 吠叫和生日時增加歲數的 function

Example:

// 
class Dog(val name: String) {
    var age: Int = 0
    var host: MutableList<String> = mutableListOf()
    val legs: Int = 4
    
    fun doRun() {
        // 寫邏輯
    }
    
    fun doEat() {
        // 寫邏輯
    }
    
    fun doSwim() {
        // 寫邏輯
    }
}

繼承( Extends ) 與介面( Interface )

上面介紹了 Class ,不免俗的還是要介紹 Extends 和 Interface ,用法上和 Java 一樣,唯一有一個小差別是 Kotlin 不需要寫 extendsimplements 關鍵字,而是用 : 代替 extends,這邊給個簡單 Java 和 Kotlin 的比較範例

  • B() 是 extends class
  • C, D, E 是 interface

Java Example:

class A extends B implements C, D, E {
    // ...
}

Kotlin Example:

class A : B(), C, D, E {
    // ...
}

看起來不同,但架構上都是一樣的,因此在撰寫上只要簡單注意一下即可喔~

Open

另外,在 java 中,除非開發者手動幫 class 加上 final 關鍵字,否則就被任意的繼承與改寫不是 final 的函式,這會導致子類別設計上可能會不符合父類別的設計初衷。而在 Kotlin 中改善了這個問題,在 Kotlin 中會預設 class 和裏面的函式是 final ,因此若要繼承 class 的話,必須加上 open 才能夠被繼承或重寫

Example:

class A : B() {
    // ...
}

open B {
    // ...
}

結論

今天介紹了 OOP 中常見的 Class 、 Constructor 、 Properties 和 Extends ,其實大致上的觀念和其他支援 OOP 語言相差無幾,只有一些小改動,基本上只要實作過幾次,應該可以很快的接軌過去。明天會繼續介紹 OOP,將 Data class 、 Sealed class 、 Enum class 和 Object 等等項目介紹完,希望能在第 11 天時順利進到 App 的部份~


另外,今天我的系列文竟然被 『范聖佑』 大大注意到了,我看到訊息時,一整個驚嚇,也加入了 Kotlin 鐵人挑戰賽的群組,原本只是想說將這半年來學習的東西做一個紀錄和回顧,沒有完賽就算了。但現在有愈來愈多人在關注,讓我也鞭策自己要更努力把 30 天的文章寫出來,才不會辜負有在關注的好朋友們~

Reference


上一篇
Day 08 | Kotlin 的 Higher-Order Function - Part 2(完結)
下一篇
Day 10 | Kotlin 的物件導向程式設計(Object-oriented programming, OOP)- Part 2( 完結 )
系列文
30天,從0開始用Kotlin寫APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言