iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
1
Mobile Development

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

Day 10 | Kotlin 的物件導向程式設計(Object-oriented programming, OOP)- Part 2( 完結 )

昨天介紹了 Class 、 Constructor 、 Properties 和 Extends ,那今天要繼續介紹各式各樣的類別,分別有

  • 枚舉類別( Enum Classes )
  • 資料類別( Data Classes )
  • 密封類別( Sealed Classes )
  • 巢狀類別( Nested Classes )
  • 內部類別( Inner Classes )

以及我個人滿愛用的 物件( Object )和 伴隨物件( Componion Object )

枚舉類別( Enum Classes )

有時候為了保證型別安全,因此會採用 Enum 列出所有的值,開發時只能使用被列出的值,那 Kotlin 中要使用 Enum ,就需要把 enum 關鍵詞加在 Class 前面
Enum classes 中也可以加上需要的 Properties 和 Functions ,這部份與 Java 都一樣

Example:

// Weather.kt
enum class Weather(private val weather: String) {
    CLEAR("Clear"), 
    CLOUDY("Cloudy"), 
    RAIN("Rain"), 
    FOG("Fog"), 
    SNOW("Snow");

    // 可以透過 enum 取得相對應的圖片
    fun drawableID(): Int {
        return when (this) {
            CLEAR -> R.drawable.clear
            CLOUDY -> R.drawable.cloudy
            RAIN -> R.drawable.rain
            FOG -> R.drawable.fog
            SNOW -> R.drawable.snow
            else -> R.drawable.clear
        }
    }
}

// Main.kt
fun main() {
    val rain = Weather.RAIN
    println(rain.name) // Output: Rain
    println(rain.ordinal) //Output: 2
}

enumValue 與 enumValueOf

從 Koltin 1.1 之後加入了 enumValueenumValueOf 來列出 enum 中所有的值,這兩個功能我在開發上是沒有使用過,但他們的使用方法很簡單

Example:

val weathers = enumValues<Weather>().joinToString { 
    "${it.ordinal} ${it.name} : value is ${it.weather} "
}

// Output:
// 0 CLEAR : value is Clear
// 1 CLOUDY : value is Cloudy
// 2 RAIN : value is Rain
// 3 FOG : value is Fog
// 4 SNOW : value is Snow

資料類別( Data Classes )

Data classes 是 Kotlin 中裝載物件資料的類別, 當 Compiler 看到 Data classes 會自動生成

  • toString()
  • equals()
  • hashCode()
  • copy()
  • componentN() 函式,對應到每個屬性的定義順序

Class 與 Data Class 的差異

Data Class 規定

Primary Constructor 必須要有至少要存在一個 parameter,且每個 paremeter 一定要是 var 或是 val
不能繼承,只能實作 Interface
Data class 不能是 abstract, open, sealed or inner
[color=Orange]

那用 Class 和 Data Class 寫出來的類別有什麼差別? 我們可以先用兩種方法實作一樣目的 Class ,那用 toString() 將兩個被實作出來的物件印出來,可以發現

  • 一般的 Class 印出來的是物件的 hashCode
  • Data class 印出來的事 Key-value 格式,比較好閱讀

Example:

fun main() {
    val dog = Dog("")
    println(dog.toString())
    // Output: Dog@5451c3a8
    
    val dogDataClass = DogDataClass()
    println(dogDataClass.toString())
    // Output: DogDataClass(name=, age=0, legs=4)
}

// 一般的 Class
class Dog(val name: String) {
    var age: Int = 0
    val legs: Int = 4
}

// Data class
data class DogDataClass (
    var name: String = "",
    var age: Int = 0,
    var legs: Int = 4
)

因此可以發現用 Data classd 可以減少很多重複性的 Code
另外, Kotlin 有提供 PairTriple 可以使用,但官方還是推荐使用 Data class ,可讀性會比較高

密封類別( Sealed Classes )

我剛開始看到 Sealed 超級問號,而且一直都沒使用,後來在讀文章的時候才知道原來他是為了限制 Class 的繼承,其實廣義來說他的目的和 Enum class 相同,但是 Enum class 只能有一個 Instance ,而 Sealed class 可以有多個不同狀態的 Instance

在使用 Sealed Class 時需要注意

  1. Sealed class 具有 open properties ,因此子類別可以直接繼承
  2. 所有子類別都必須與 Sealed class 在同個文件中宣告
  3. Sealed class 本身已經是抽象,因此不能被實作

主要會使用 Sealed class 的場景多為搭配 when ,例如很適合拿來實作 Error Handling
像下面的例子就是一種實作 Error Handling 的方法,有幾個地方可以注意

  1. 先宣告出 Resultsealed class,並且有兩個泛行參數 <out T, out E>
  2. 宣告兩個 class, 分別為 Ok 以及 Err,並解都繼承 Result

Ok :如果沒有 Error 時,那會回傳指定的型態 T
Err :如果有錯誤,那就會回傳指定的型態 E
[color=Orange]

Example:

// Error Handling
sealed class Result<out T, out E>
class Ok<out T>(val value: T) : Result<T, Nothing>()
class Err<out E>(val error: E) : Result<Nothing, E>()

那在使用上可以像這樣用

Example:

fun main() {
    when(val result = readFile('./some/file.txt')) {
        is Ok -> println("Read file success. Size of file is $result.value.length()")
        is Err -> println(result.error) 
        // 如果成功會 Output 檔案的大小
        // 如果失敗會 Output "Get some error when reading file"
    }
}

fun readFile(path: String): Result<File, Error> {

    // 開始讀檔 ...
    val file = File(path)
    
    if( file.length() > 0 ) {
        return Ok(File)
    } else {
        // 這邊只是簡單解釋一下,事實上使用 try-catch 回傳 exception message 會更好
        return Err(Error("Get some error when reading file"))
    }
}

巢狀類別( Nested Classes ) 與 內部類別( Inner Classes )

Nested Classes 很容易理解,就是在 Class 中定義出另一個 Class,就是 Nested Class, 但是 Nested Class 不能持有外部類別

Example:

class Dog(val name: String) {
    var age: Int = 0
    val legs: Int = 4
    
    class dogNestedClass {
        fun show() {
            println(legs) // Error ,不能持有外部類別,因此不能用外部類別的成員
        }
    }
}

Inner Classes 也類似於 Nested Classes,但是他可以持有外部類別的成員

Example:

class Dog(val name: String) {
    var age: Int = 0
    val legs: Int = 4
    
    inner class dogNestedClass {
        fun show() {
            println(legs) // Success ,會印出 4
        }
    }
}

Singleton 與 Companion Object

Singleton 是很長使用的一種設計模式,可以保證一個 Class 在 Memory 中只會有一個 Instance,這樣有利於協調系統整體的行為,才不會生出一堆又互相 Dependency 或互相影響,但在如何控管 Singleton 不會有搶資源或衝突上就又是另一個煩惱了

那 Kotlin 中的 Object 本身就實現了 Singleton 的效果,初始化後不會再改變,像是下面的例子,我們就幫 Bob 建立出一個只屬於他的 Object

Example:

// Bob.kt
object Bob {
    val age: Int = 3
    fun md5Password() = EncoderByMd5(age)
}

// Main.kt
fun main() {
    val bobAge = Bob.age
    val bobPassword = Bob.md5Password()
    println(bobAge)      // Output: 3
    println(bobPassword) // Output: md5後的值
}

而 Kotln 中因為沒有像是 Java class 中有 static 的區塊,因此就要提到 Companion Object ,他等同於 static ,會存在 Class 之下

那下面的例子可以注意到

  • class 中,存在一個 companion object 的區塊,區塊中建立了一個 TAG 的 String
  • main() 中要取用 DogTAG 時,必須要加上 Companion 才可取得

Example:

// Dog.kt
class Dog{

    // 相當於 Java 中 static 的區塊
    companion object {
        val TAG: String = "Dog"
    }
}

// Main.kt
fun main() {
    println(Dog.Companion.TAG) // Output: Dog
}

ObjectCompanion Object 在 Kotlin 都滿常會被使用到的,這邊我非常推荐可以讀這篇文章,我個人認為寫的非常詳細,推推!!!

結論

終於趕在 11:40 分之前把文章趕完了,今天一個腎上腺素飆生RRRRRRR!!!!!/images/emoticon/emoticon06.gif
今天也講完 OOP,也相當於講完 Kotlin 比較基礎的部份,但其實還有很多眉眉角角還沒提到,之後在開始寫 App 的時候會在慢慢補起來的
另外,這邊也要推推 Kotlin 鐵人陣 團隊也開賽囉,裡面有很多很強的大大,寫的文章都很有深度,也推荐大家去讀讀看,一定會有幫助的

Reference


上一篇
Day 09 | Kotlin 的物件導向程式設計(Object-oriented programming, OOP)- Part 1
下一篇
Day 11 | 開發架構演化史: MVC -> MVP -> MVVM
系列文
30天,從0開始用Kotlin寫APP30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言