Kotlin 中的類別與物件例項

1. 事前準備

在本課程的程式碼研究室,您會建構 Dice Roller Android 應用程式。使用者「擲骰子」時,系統會隨機產生結果。系統在產生結果時會考慮到骰子的面數。舉例來說,6 面的骰子只能得出 1 到 6 的值。

最終應用程式應如下所示。

8299aaca25c93863.png

為了協助您專注瞭解這個應用程式的最新程式設計概念,您會使用 Kotlin 程式設計工具為基礎的瀏覽器,來建立核心應用程式功能。該程式會將結果輸出至主控台。您之後會在 Android Studio 中實作使用者介面。

在第一個程式碼研究室中,您要建立 Kotlin 程式來模擬擲骰子的動作,並輸出隨機數字,就像實際擲骰子一樣。

必要條件

  • 瞭解如何在 https://developer.android.com/training/kotlinplayground 中開啟、編輯及執行程式碼
  • 建立並執行使用變數和函式的 Kotlin 程式,然後將結果輸出至主控台。
  • 使用採用 ${variable} 標記法的字串範本,來設定文字內的數字格式。

課程內容

  • 如何透過程式輔助方式產生隨機數字來模擬擲骰子的動作。
  • 如何透過建立含有變數和方法的 Dice 類別來建構程式碼。
  • 如何建立類別的物件例項、修改其變數,以及呼叫其方法。

建構項目

  • 使用 Kotlin 程式工具為基礎的瀏覽器,建立可執隨機擲骰子的 Kotlin 程式。

軟硬體需求

  • 已連上網際網路的電腦

2. 投擲隨機數字

遊戲通常都有隨機的特性,讓您贏得隨機獎勵,或是在棋盤上隨機移動步數。在日常生活中,您可以使用隨機數字和字母來產生更安全的密碼!

您可以編寫程式模擬擲骰子的動作,而不必真的擲骰子。每次擲骰子時,可能值範圍內的任何數字都可以是結果。幸運的是,你並不需要為這類程式建構自己的隨機數字產生器。包括 Kotlin 在內的多數程式設計語言都內建產生隨機數字的方法。在這項工作中,您會使用 Kotlin 程式碼產生一個隨機數字。

設定範例程式碼

  1. 在瀏覽器中開啟網站:https://developer.android.com/training/kotlinplayground
  2. 刪除程式碼編輯器中所有現有程式碼,並以下方程式碼取代。這是您在之前程式碼研究室中使用的 main() 函式。
fun main() {

}

使用隨機函式

如要擲骰子,您需要設法呈現所有有效的骰子值。一般的 6 面骰子,可接受的結果有 1、2、3、4、5 和 6。

你在先前的課程中已學會資料類型,例如 Int 代表整數,String 代表文字。IntRange 是另一種資料類型,代表從起點到終點的整數範圍。IntRange 是適合的資料類型,用於代表骰子可能產生的值。

  1. main() 函式中,請將變數定義為名為 diceRangeval。請將其指派給 1 到 6 的 IntRange,代表 6 面骰子可以擲出的整數範圍。
val diceRange = 1..6

可以看出,1..6 是 Kotlin 範圍,因為它包含開始數字、兩個點,後面接著是結束號碼數字 (中間沒有空格)。以下列舉其他整數範圍的例子:2..5 代表數字 2 到 5,100..200 代表數字 100 到 200。

與呼叫 println() 會指示系統列印指定文字的做法類似,您可以使用名為 random() 的函式,產生並傳回指定範圍的隨機數字。和之前一樣,您可以將結果儲存在變數中。

  1. main() 中,將變數定義為名為 randomNumberval
  2. 請為 randomNumber 設定對 diceRange 範圍呼叫 random() 的結果,如下所示。
 val randomNumber = diceRange.random()

請注意,在變數與函式呼叫之間使用句號或一點,即可在 diceRange 呼叫 random()。您可以將其解讀為「從 diceRange 產生隨機數字」,之後,結果就會儲存在 randomNumber 變數中。

  1. 如要查看隨機產生的數字,請使用字串格式標記法 (也稱為「字串範本」) ${randomNumber} 輸出,如下所示。
println("Random number: ${randomNumber}")

完成的程式碼應如下所示。

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. 執行程式碼數次。您每次都應看到以下的輸出內容,但隨機數字不一樣。
Random number: 4

3. 建立骰子類別

擲骰子時,骰子是你手中的真實物品。您剛才編寫的程式碼雖然可以正常運作,但很難讓人聯想到真正的骰子。編寫程式時,如果讓程式更貼近所代表的事物,會讓人更容易理解。如果能用程式設計方式,建立一個可以投擲的骰子,那就太棒了!

所有骰子的運作原理都大同小異。它們有相同的屬性 (例如有多個面),而且有相同的行為 (例如可以投擲)。在 Kotlin 中,您可以建立骰子的程式輔助藍圖,指出骰子有多個面,而且可以擲出隨機數字。此藍圖稱為類別

接下來,就可以透過該類別建立實際的骰子物件,稱為「物件執行個體」。例如,您可以建立有 12 個面或 4 個面的骰子。

定義骰子類別

在下列步驟中,您會定義名為 Dice 的新類別來代表可投擲的骰子。

  1. 為了重新開始,請清除 main() 函式中的程式碼,最終程式碼應如下所示。
fun main() {

}
  1. 在這個 main() 函式下方,新增空白行,然後新增程式碼來建立 Dice 類別。如下所示,請先輸入關鍵字 class,然後輸入類別名稱,後面加左右大括號。請在左右大括號之間留出空格,以便放入類別的程式碼。
class Dice {

}

在類別定義中,您可以使用變數為類別指定一或多項屬性。真實的骰子可以有多個面、一種顏色或重量。在這項工作中,您的重點會放在骰子面數的屬性上。

  1. Dice 類別中,新增名為 sidesvar 做為骰子的面數。將 sides 設為 6。
class Dice {
    var sides = 6
}

大功告成了。現在您有一個很簡單的類別,代表骰子。

建立骰子類別的執行個體

有了這個 Dice 類別,等同您掌握骰子的藍圖。如要讓程式中「實際」顯示骰子,就需要建立 Dice 物件例項。(如果您需要三個骰子,就要建立三個物件執行個體。)

ba2038022410942c.jpeg

  1. 如要建立 Dice 的物件執行個體,請在 main() 函式中建立名為 myFirstDiceval,並將其初始化為 Dice 類別的執行個體。請注意類別名稱之後的括號,代表您要從類別建立新的物件執行個體。
fun main() {
    val myFirstDice = Dice()
}

現在,您已經有了根據藍圖建立的 myFirstDice 物件,便可以存取其屬性。Dice 唯一的屬性是 sides。您可以使用「點標記法」存取屬性。因此,如要存取 myFirstDicesides 屬性,您必須呼叫 myFirstDice.sides,讀音為「myFirstDicesides」。

  1. myFirstDice 宣告下方,新增 println() 陳述式,以輸出 myFirstDice.sides 數量
println(myFirstDice.sides)

您的程式碼應如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. 執行程式,它應會輸出 Dice 類別中定義的 sides 數量。
6

現在,您有一個 Dice 類別,以及一個有 6 個 sides 的實際骰子 myFirstDice

我們來擲骰子吧!

擲骰子

您之前用了函式來執行輸出蛋糕圖層的動作。擲骰子操作也可以用函式來實作。由於所有骰子都可以投擲,因此您可以在 Dice 類別中新增用來擲骰子的函式。在類別中定義的函式也稱為「方法」

  1. Dice 類別 sides 變數下方﹐插入一行空行,然後建立新的函式來擲骰子。請依序輸入 Kotlin 關鍵字 fun、方法的名稱、括號 () 以及左右大括號 {}。您可以在左右大括號之間留出空行,以便輸入更多程式碼,如下所示。您的類別應如下所示。
class Dice {
    var sides = 6

    fun roll() {

    }
}

擲出六面骰子時,系統會產生 1 到 6 之間的隨機數字。

  1. roll() 方法中建立一個 val randomNumber。在 1..6 範圍內指派一個隨機數字。使用點標記法對該範圍呼叫 random()
val randomNumber = (1..6).random()
  1. 產生隨機號碼後,將其輸出至主控台。完成的 roll() 方法應如以下程式碼所示。
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. 如要實際投擲 myFirstDice,請在 main() 中對 myFirstDice 呼叫 roll() 方法。您可以使用「點標記法」來呼叫方法。那麼,如要對 myFirstDice 呼叫 roll() 方法,請輸入 myFirstDice.roll(),讀音為「myFirstDiceroll()」。
myFirstDice.roll()

完成的程式碼應如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. 執行程式碼!你應會看到隨機擲出的骰子數字小於骰子面數。執行程式碼數次後,您會發現面數維持不變,而擲骰子結果的值則會改變。
6
4

恭喜!您已使用 sides 變數和 roll() 函式來定義 Dice 類別。在 main() 函式中,您已建立新的 Dice 物件執行個體,然後對該執行個體呼叫 roll() 方法,以產生隨機數字。

4. 傳回擲骰子的結果值

目前您在 roll() 函式中輸出 randomNumber 的值,一切運作正常!但在某些情況下,將函式結果傳回給呼叫該函式的對象會更為實用。舉例來說,您可以將 roll() 方法的結果指派給變數,然後為玩家移動相應的步數。我們來看看具體操作。

  1. main() 中,修改顯示 myFirstDice.roll() 的行。建立名為 diceRollval。將其設為與 roll() 方法傳回的值相同。
val diceRoll = myFirstDice.roll()

目前還沒有任何變化,因為 roll() 尚未傳回任何內容。roll() 必須傳回一些內容,這個程式碼才會如預期般運作。

在先前的程式碼研究室中,您瞭解到需要為函式的輸入引數指定資料類型。同理,您需要為函式傳回的資料指定資料類型。

  1. 變更 roll() 函式,以指定要傳回的資料類型。在本例子中,隨機數字是 Int,因此傳回類型為 Int。指定傳回類型的語法如下:在函式名稱後面,在括號後加上冒號和空格,然後為函式的傳回類型加上 Int 關鍵字。函式定義的程式碼應如下所示。
fun roll(): Int {
  1. 執行這個程式碼。問題檢視畫面中會顯示錯誤訊息。內容如下:
A ‘return' expression required in a function with a block body (‘{...}')

您已將函式定義變更為傳回 Int,但系統提示您程式碼實際上並未傳回 Int。「區塊內文」或「函式主體」是指函式的大括號之間的程式碼。您可以修正這個錯誤,做法是在函式主體結尾使用 return 陳述式傳回函式的值。

  1. roll() 中移除 println() 陳述式,並以 randomNumberreturn 來取代。roll() 函式的程式碼應如下所示。
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. main() 中,移除骰子面的輸出陳述式。
  2. 新增陳述式,以詳盡的說明句子輸出 sidesdiceRoll 的值。完成的 main() 函式應會類似下列程式碼。
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. 執行程式碼,輸出結果應如下所示。
Your 6 sided dice rolled 4!

以下是目前為止的所有程式碼。

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

5. 變更骰子面數

不是所有骰子都有 6 面!骰子有各種形狀和尺寸,有 4 面、8 面,最多可到 120 面!

  1. Dice 類別的 roll() 方法中,將硬式編碼的 1..6 改為使用 sides。這樣,範圍與擲出的隨機數字便一律適用於面數。
val randomNumber = (1..sides).random()
  1. main() 函式中,在擲出的骰子結果下方,將 myFirstDicesides 變更為 20。
myFirstDice.sides = 20
  1. 複製下方的現有輸出陳述式,然後貼到變更面數的後面。
  2. myFirstDice 的輸出結果替換為對 diceRoll 呼叫 roll() 方法的輸出結果。
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")

你的程式應如下所示。

fun main() {

    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. 執行程式,然後您應該會看到一則關於 6 面骰子的訊息,以及第二則關於 20 面骰子的訊息。
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

6. 自訂骰子

類別的概念是代表物件,通常是現實世界中的實物。在本例子中,Dice 類別代表實際的骰子。在現實世界中,骰子不可能改變面數。如果您需要不同的面數,便需要用另一個骰子。在程式輔助方面,代表您應建立新的骰子物件執行個體,其中包含所需的面數,而不是變更現有 Dice 物件執行個體的面屬性。

在這項工作中,您要修改 Dice 類別,以便在建立新的執行個體時指定面數。變更 Dice 類別定義就能提供面數,這與函式接受輸入引數的方式類似。

  1. 修改 Dice 類別定義,以接受名為 numSides 的整數。類別中的程式碼不會改變。
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dice 類別中刪除 sides 變數,因為您現在可以使用 numSides 了。
  2. 此外,請將範圍修正為使用 numSides

您的 Dice 類別應如下所示。

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

執行這個程式碼時,您會看到很多錯誤,因為您必須更新 main(),才能處理對 Dice 類別所做的變更。

  1. main() 中、如要建立含有 6 個面的 myFirstDice,您現在必須提供面數做為 Dice 類別的引數,如下所示。
    val myFirstDice = Dice(6)
  1. 在輸出陳述式中,將 sides 變更為 numSides
  2. 然後在下方,刪除將 sides 變更為 20 的程式碼,因為該變數已不再存在。
  3. 請一併刪除下方的 println 陳述式。

您的 main() 函式應如下列程式碼所示,執行這段程式碼應該不會再出現錯誤。

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. 輸出第一個擲骰子結果後,新增程式碼來建立並輸出第二個名為 mySecondDiceDice 物件,這個骰子包含 20 個面。
val mySecondDice = Dice(20)
  1. 新增用於投擲和輸出傳回值的輸出陳述式。
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. 完成的 main() 函式應如下所示。
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. 執行完成的程式,輸出內容應如下所示。
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

7. 採用完善的程式設計做法

編寫程式碼時,應保持簡潔。您可以去除 randomNumber 變數,並直接傳回隨機數字。

  1. 變更 return 陳述式以直接傳回隨機數字。
fun roll(): Int {
    return (1..numSides).random()
}

在第二個輸出陳述式中,您會將用於取得隨機數字的呼叫,放入字串範本中。您可以運用在第一個輸出陳述式中相同的做法,去除 diceRoll 變數。

  1. 在字串範本中呼叫 myFirstDice.roll() 並刪除 diceRoll 變數。main() 程式碼的前兩行現在應如下所示。
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. 執行程式碼,輸出內容應該氶有任何不同。

這是您重構後的最終程式碼。

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

8. 解決方案程式碼

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

9. 總結

  • IntRange 呼叫 random() 函式以產生隨機數字:(1..6).random()
  • 類別就像是物件的藍圖,會包含屬性和行為,可做為變數和函式來實作。
  • 類別的執行個體代表一個物件,通常是實物,例如骰子。您可以對物件呼叫動作,並變更其屬性。
  • 您可以在建立執行個體時,為類別提供值。例如:class Dice(val numSides: Int),然後使用 Dice(6) 建立執行個體。
  • 函式可以傳回結果。在函式定義中指定要傳回的資料類型,並在函式主體中使用 return 來傳回一些內容。例如:fun example(): Int { return 5 }

10. 瞭解詳情

11. 自行練習

請練習下列項目:

  • Dice 類別提供另一個顏色的屬性,然後建立多個有不同面數和顏色的骰子例項!
  • 建立 Coin 類別,讓它能夠翻轉、建立類別執行個體,以及拋擲一些硬幣!您會如何使用 random() 函式搭配範圍來完成硬幣拋擲動作呢?