iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 10
1
  • 線上 Kotlin 練習編譯器:https://try.kotlinlang.org/
  • 教學內容會融入先前章節提過的知識,若遇到問題可以從前面的章節依序閱讀

  今天快速進入課程主題 Class (類別),在 Kotlin 中類別的宣告如下:

https://ithelp.ithome.com.tw/upload/images/20181024/20111944C98bOOUXda.png

  上圖程式架構中,Customer() 是一個最純粹的類別。若想在建立類別的同時,強制指定必須要傳入參數進行初始,否則拒絕建立的時候,就以 CustomerA 方式呈現,其實這個機制稱為建構子,完整的寫法在 CustomerB 展示,A 與 B 的寫法呈現的結果是完全相同的,所以一般在程式設計時可以省略 constructor (建構子) 關鍵字。

  你一定想問,為什麼還要有 constructor 呢?那是因為省略這個關鍵字的前提是:不指定任何的可見性修飾詞 (private, protected, internal, public),也不需要使用程式註釋,若是有使用到其中一個,就需要加上 construcotr 關鍵字,如 CustomerC()


  上段提到的程式註釋,這裡舉其中一種用法 @Deprecated 說明,此註釋主要是將舊的程式碼註記棄用,改建議使用新的寫法,在 IDE 使用到時,就會如下圖所示出現提示訊息,有興趣了解更多註釋用法可以搜尋關鍵字:Kotlin annotations。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944HjpgrN6jXY.png


  當宣告好了類別需要使用時,就以程式碼 var 物件名稱 = 類別名稱() 的方式使用,與其它程式語言比較,省略了 new 關鍵字,下圖的 Line: 14, 15 行建立了兩個物件,另外,這邊可以發現兩種類別宣告方式,CustomerA 在建構子中加入了 val、var,此時就會自動在類別內生成兩個屬性,屬性的意思稍微後面一點說明。

  CustomerB 則是在建構子註明了接受哪些參數,另外的在類別內自訂兩個屬性 (Line: 9, 10),這邊需注意的是就算你的建構子參數與類別屬性為相同名稱,也是視為不同的,所以並不會衝突,但是不利於程式易讀性,因此建議在建構子參數中以底線為開頭,詳細示範在之後的段落介紹。

  下圖的示範重點是,當宣告一個新的 CustomerB 實體,初始設定的 _account 透過建構子方式指定時,可以在類別內設計通通轉換為小寫儲存在屬性 account 上。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944OsWiy1uCUJ.png


  接著示範一個無建構子的類別,純粹的宣告一個類別內含四個屬性存放資料,屬性全部都有指定預設值,如此一來在建立物件時才不會發生錯誤。在 Line: 16, 17 試著修改一個 val 屬性時,系統會發出錯誤,你可以把 val 當作是唯獨,除了初始值之外,就不得再修改。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944uQdQY9lX3H.png

  屬性的取用方式是以 物件名稱.屬性名稱,在 Line: 19, 20 展示。特別一提,若在程式中使用 LocalDate 時間型態的話,要在最上方區段加入 import java.time.LocalDate


  下圖中的建構子參數名稱命名方式採用剛剛提到的底線開頭,主要用於識別參數與屬性,另外在 main 區塊,展示了建立物件時,除了使用建構子參數外,也能使用 object initializers (物件初始設定式),加上 .apply() { … },可省略如上圖 Line: 14, 15 每次賦予值時都要加上物件名稱。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944tqUaQ8F51e.png


  可能會好奇屬性宣告跟建構子參數宣告在實務上的差別,下圖示範的是將屬性提到建構子參數宣告,可以很清楚的發現,無論在宣告類別或建立物件時,你將會得到一整段又長又扁平的程式碼,當你的類別一多,對於程式可維護性和易用性來說,並不是一件好事。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944V3mBR6ygyW.png

  不過作者也有看到過下圖的呈現方式,將建構子每個參數用換行分隔,提供給各位參考:

https://ithelp.ithome.com.tw/upload/images/20181024/201119449PeIJrLEIt.png


  類別的構成除了建構子外,還有 init (初始化) 及 second constructor (次建構子) 可以使用,初始化表示物件建立時,一定會優先執行到的部分;次建構子則可提供彈性的設計。

  下圖的編號清楚的編排出程式執行順序,各位可從 fun main紅色 1 開始追蹤,接續到 class Customer (紅 2),建立物件時,首先會執行到 init (紅 3) 內的程式,因此在右邊結果可看到灰色 3,接續到屬性的宣告,這邊有使用 also(::println) 協助列出結果,在正常程式設計時不需這樣使用。

https://ithelp.ithome.com.tw/upload/images/20181024/20111944h50ToWrcYR.png

  執行完 4 ~ 6 後,因為 custA 並無使用到建構子,到這邊算是完成了 custA 的物件建立。接著看藍色 7 custB,執行步驟跟紅色 custA 差不多,不過這邊有特別指定了次建構子,在流程 8 透過 this 關鍵字將其中兩個參數傳至主建構子使用,等完成了跟 custA 一樣的流程時,會回到藍 14 繼續執行 15 ~ 16

  了解物件的內部執行流程,能避免錯誤順序邏輯導致的設計錯誤,你可試著幫自己的程式加入斷點,來看看整體流程是否符合預期。


  在類別中使用屬性可以搭配 privategetter、setter 來靈活使用, private setsetter 變為私有,宣告在屬性的下方,account 此時限制在外部使用時,僅能讀取無法修改

    public var account: String = _account.toLowerCase()
        private set

  private var 直接將整個屬性變成私有,外部建立物件時,完全無法讀取與修改這個屬性。

    public var password: String = _password

  透過 getter 可以在取值時,基於其它邏輯條件決定要回傳的值,以下範例情境為:為當月份壽星時,自動視為 VIP,若非當月壽星,則傳回一般 VIP 註記,這邊也將 _isVIP 變更為 private,若要修改值,只能透過公開的方法來實作。

    private var _isVIP: Boolean = false
    public val isVIP: Boolean
        get() {
            if (birthday?.getMonth() == LocalDate.now().getMonth())
            {
                return true
            }
            
            return _isVIP;
        }

  這邊以更改密碼為例,需要在類別中建立一個公開的方法,再透過 this 關鍵字將類別內部隱藏的屬性更新

    fun changePassword(_newPassword: String) {
        this.password = _newPassword;
    }

  完整範例可以參考下圖,可以看到第 24、25 的用法會造成程式發生錯誤,皆是因為 private 的關係,Line: 26 是正確用法。

https://ithelp.ithome.com.tw/upload/images/20181024/201119440IZaQ9aMMf.png


  最後,要講解的是繼承的概念,我們以使用者當作主體,再區分為一般顧客或員工,繼承主要是讓類別能夠共用部分設計架構之外,又能保有個體的特色,詳細概念在此不多述,我們關注在 Kotlin 的使用方式。要繼承一個類別使用冒號 : 達成 (Line: 19, 36),要被繼承的類別則要加上 open 關鍵字 (Line: 3),一旦繼承時,除了 private 屬性 (Line: 5) 或方法之外,其它都可以繼承到子類別上,所以即便在 Customer 中並沒有宣告 account,但你仍可以透過 cust.account 取得帳號,因為繼承來自主類別 User

https://ithelp.ithome.com.tw/upload/images/20181024/201119441smWhGfXLa.png

  子類別無法存取到在繼承對象中被設定成 private 的內容,一樣要透過繼承的主類別 public 方法 changePassword 才能變更到 private password 屬性,以範例來說,可以透過 employee.changePassword() 方法來變更密碼屬性,縱使 class Employee 當中並沒有該方法,不過經由繼承 User 後就能夠使用。

  繼承當中也有覆寫的概念,以 override 當作關鍵字 (Line: 31, 40),可以在子類別中自行設計同名的方法,在實作時也會直接以子類別的方法執行,若搭配 super 關鍵字 (Line: 41),則既可以往上層執行主類別的方法,接續還可以執行子類別的方法 (或顛倒順序)。

  這個部分用文字形容會難以消化,請參照上圖示範,自行動手嘗試最終會印出什麼結果!今日的課程就到這邊,想知道更詳細的物件導向內容至下方閱讀參考資料,我們明天見!


資料參考

Classes and Inheritance - Kotlin Programming Language
https://kotlinlang.org/docs/reference/classes.html

Visibility Modifiers - Kotlin Programming Language
https://kotlinlang.org/docs/reference/visibility-modifiers.html

Properties and Fields - Kotlin Programming Language
https://kotlinlang.org/docs/reference/properties.html


上一篇
Day 09. Kotlin 語言學習 - 迴圈控制
下一篇
Day 11. Kotlin Lambda
系列文
Kotlin for Android30

1 則留言

0
藍諾
iT邦新手 5 級 ‧ 2018-11-05 11:01:42

今天回來看本篇文章發現內容的:類別/物件,有劃分不明確的部分,特別修正了一下,對之前的刊誤部分,若有造成概念上的認知混淆感到抱歉。

我要留言

立即登入留言