Classes et instances d'objets dans Kotlin

1. Avant de commencer

Pour les ateliers de programmation de ce parcours, vous allez créer une application Android "Dice Roller", qui génère un résultat aléatoire lorsque l'utilisateur lance le dé. Le résultat tient compte du nombre de faces des dés. Par exemple, un dé à six faces ne peut générer que des valeurs comprises entre 1 et 6.

Voici à quoi ressemblera l'application finale.

8299aaca25c93863.png

Pour vous aider à mieux vous concentrer sur les nouveaux concepts de programmation, vous utiliserez l'outil de programmation Kotlin sur navigateur pour créer les principales fonctionnalités de cette application. Le programme affichera vos résultats dans la console. Par la suite, vous implémenterez l'interface utilisateur dans Android Studio.

Dans ce premier atelier de programmation, vous allez créer un programme Kotlin qui simule un lancer de dé et génère un nombre aléatoire, comme le ferait un dé physique.

Conditions préalables

  • Vous savez ouvrir, modifier et exécuter du code dans https://developer.android.com/training/kotlinplayground.
  • Vous savez créer et exécuter un programme Kotlin qui utilise des variables et des fonctions, puis affiche un résultat dans la console.
  • Vous savez mettre en forme les nombres dans un texte en utilisant un modèle de chaîne avec la notation ${variable}.

Points abordés

  • Comment programmer la génération de nombres aléatoires pour simuler des lancers de dés
  • Comment structurer votre code en créant une classe Dice avec une variable et une méthode
  • Comment créer une instance d'objet d'une classe, modifier ses variables et appeler ses méthodes

Objectifs de l'atelier

  • Créer un programme Kotlin à l'aide de l'outil de programmation Kotlin sur navigateur, qui permet de simuler des lancers de dés aux résultats aléatoires

Ce dont vous avez besoin

  • Un ordinateur avec une connexion Internet

2. Générer des nombres aléatoires

Les jeux comportent souvent un élément aléatoire. Vous pouvez gagner une récompense au hasard ou avancer d'un nombre aléatoire d'étapes sur le plateau de jeu. Dans la vie de tous les jours, vous pouvez utiliser des chiffres et des lettres au hasard pour générer des mots de passe plus sûrs.

Au lieu de lancer de vrais dés, vous pouvez écrire un programme qui simule le processus. Chaque fois que vous lancez le dé, le résultat peut être n'importe quel nombre compris dans la plage de valeurs possibles. Heureusement, vous n'avez pas besoin de créer votre propre générateur de nombres aléatoires pour ce type de programme. La plupart des langages de programmation, y compris Kotlin, permettent de générer des nombres au hasard. Dans cette tâche, vous allez générer un nombre aléatoire à l'aide du code Kotlin.

Configurer votre code de démarrage

  1. Dans votre navigateur, ouvrez le site Web https://developer.android.com/training/kotlinplayground.
  2. Supprimez tout le code existant dans l'éditeur de code et remplacez-le par le code ci-dessous. Il s'agit de la fonction main(), avec laquelle vous avez travaillé dans de précédents ateliers de programmation.
fun main() {

}

Utiliser la fonction aléatoire

Pour lancer les dés, vous avez besoin d'un moyen de représenter toutes les valeurs valides. Pour les dés à six faces, les résultats possibles sont 1, 2, 3, 4, 5 et 6.

Précédemment, vous avez appris qu'il existe des types de données tels que Int pour les nombres entiers et String pour le texte. IntRange est un autre type de données, qui représente une plage de nombres entiers comprise entre deux bornes. Le type de données IntRange peut servir à représenter les valeurs possibles d'un lancer de dés.

  1. Dans votre fonction main(), définissez une variable en tant que val et nommez-la diceRange. Affectez-la à une fonction IntRange de plage 1 à 6, qui représentera la plage d'entiers qu'un dé à six faces peut produire.
val diceRange = 1..6

Vous pouvez identifier 1..6 comme une plage Kotlin, car elle contient un numéro de début, deux points, suivi d'un numéro de fin (sans espace entre les deux). D'autres exemples de plages d'entiers seraient 2..5, pour des nombres entre 2 et 5, ou 100..200, pour des nombres entre 100 et 200.

De la même manière que l'appel de println() indique au système d'afficher le texte donné, vous pouvez utiliser une fonction random() pour générer et renvoyer un nombre aléatoire compris dans une plage donnée. Comme précédemment, vous pouvez stocker le résultat dans une variable.

  1. Dans main(), définissez une variable en tant que val et nommez-la randomNumber.
  2. Définissez randomNumber sur la valeur du résultat de l'appel de random() dans la plage diceRange, comme indiqué ci-dessous.
 val randomNumber = diceRange.random()

Notez que vous appelez random() dans la plage diceRange à l'aide d'un point inséré entre la variable et l'appel de fonction. Vous pouvez interpréter ceci comme "générer un nombre aléatoire issu de diceRange". Le résultat est ensuite stocké dans la variable randomNumber.

  1. Pour afficher le nombre généré de façon aléatoire, utilisez la notation de mise en forme de chaîne (également appelée "modèle de chaîne") ${randomNumber}, comme illustré ci-dessous.
println("Random number: ${randomNumber}")

Votre code, une fois terminé, doit se présenter comme suit :

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. Exécutez votre code plusieurs fois. Chaque fois, la sortie doit s'afficher comme ci-dessous, avec des nombres aléatoires différents.
Random number: 4

3. Créer une classe Dice

Lorsque vous lancez un dé, il s'agit d'un objet physique que vous avez en main. Même si le code que vous venez d'écrire fonctionne parfaitement, il est difficile de faire le lien avec de véritables dés. Organiser un programme pour le rapprocher de ce qu'il représente facilite la compréhension. Ce serait bien de pouvoir "lancer" nos dés programmatiques !

Tous les dés fonctionnent essentiellement de la même manière. Ils ont les mêmes propriétés, comme leurs faces, et le même comportement, comme la capacité à être lancés. Dans Kotlin, vous pouvez créer un schéma programmatique pour refléter un dé avec des faces et la capacité de produire un nombre aléatoire. On appelle ce type de schéma une classe.

À partir de cette classe, vous pourrez créer des objets "dés", appelés instances d'objet. Par exemple, vous pouvez créer des dés à 4 ou 12 faces.

Définir une classe Dice

Dans les étapes suivantes, vous allez définir une nouvelle classe appelée Dice (dé en anglais) pour représenter un dé "lançable".

  1. Pour repartir sur des bases saines, supprimez le code de la fonction main() afin d'obtenir le code présenté ci-dessous.
fun main() {

}
  1. Sous cette fonction main(), ajoutez une ligne vide, puis du code pour créer la classe Dice. Comme indiqué ci-dessous, commencez par le mot clé class, suivi du nom de la classe, puis d'accolades ouvrantes et fermantes. Laissez un espace entre les accolades pour insérer le code de la classe.
class Dice {

}

Dans la définition d'une classe, vous pouvez lui spécifier une ou plusieurs propriétés à l'aide de variables. Les dés physiques ont un nombre de faces, une couleur et un poids. Dans cette tâche, votre priorité sera la propriété du nombre de faces de vos dés.

  1. Dans la classe Dice, ajoutez une var appelée sides correspondant au nombre de faces de vos dés. Définissez sides sur 6.
class Dice {
    var sides = 6
}

Et voilà ! Vous disposez désormais d'une classe très simple représentant votre dé.

Créer une instance de la classe Dice

Avec cette classe Dice, vous disposez d'un schéma de construction pour votre dé. Pour intégrer un dé à votre programme, vous devez créer une instance d'objet Dice. (Et si vous avez besoin de trois dés, vous devez créer trois de ces instances d'objets.)

ba2038022410942c.jpeg

  1. Pour créer une instance d'objet de Dice, dans la fonction main(), créez une val appelée myFirstDice et initialisez-la en tant qu'instance de la classe Dice. Notez les parenthèses après le nom de la classe. Elles indiquent que vous créez une instance d'objet à partir de la classe.
fun main() {
    val myFirstDice = Dice()
}

Maintenant que vous disposez d'un objet myFirstDice, créé à partir du schéma, vous pouvez accéder à ses propriétés. La seule propriété de Dice est sides. Vous accédez à une propriété à l'aide de la "notation par points". Pour accéder à la propriété sides de myFirstDice, vous appelez myFirstDice.sides (qui se prononce "myFirstDice-dot-sides", "dot" signifiant "point" en anglais).

  1. Sous la déclaration de myFirstDice, ajoutez une instruction println() pour récupérer le nombre de sides de myFirstDice..
println(myFirstDice.sides)

Le code doit se présenter comme suit.

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

class Dice {
    var sides = 6
}
  1. Exécutez votre programme. Il devrait générer le nombre de sides défini dans la classe Dice.
6

Vous disposez maintenant d'une classe Dice et d'un myFirstDice avec 6 sides.

Il est de temps de le lancer le dé !

Lancer le dé

Vous avez précédemment utilisé une fonction pour effectuer une action : imprimer les étages d'un gâteau. L'action de lancer un dé peut aussi être implémentée en tant que fonction. Et comme tous les dés peuvent être lancés, vous pouvez ajouter une fonction pour cela dans votre classe Dice. Une fonction définie dans une classe est également appelée méthode.

  1. Dans la classe Dice, sous la variable sides, insérez une ligne vide, puis créez une fonction permettant de lancer votre dé. Commencez par le mot clé Kotlin fun, suivi du nom de la méthode, suivi des parenthèses (), puis des accolades {}. Vous pouvez laisser une ligne vide entre les accolades pour laisser de l'espace à du code complémentaire, comme illustré ci-dessous. Votre classe devrait se présenter comme suit :
class Dice {
    var sides = 6

    fun roll() {

    }
}

Lorsque vous lancez un dé à six faces, vous générez un nombre aléatoire compris entre 1 et 6.

  1. Dans la méthode roll(), créez une val randomNumber. Attribuez-lui un nombre aléatoire issu de la plage 1..6. Utilisez la notation par points pour appeler random() sur la plage.
val randomNumber = (1..6).random()
  1. Une fois le nombre aléatoire généré, affichez-le dans la console. Votre méthode roll() terminée devrait se présenter comme suit.
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. Pour lancer myFirstDice, dans main(), appelez la méthode roll() sur myFirstDice. Les méthodes sont appelées à l'aide de la notation par points. Pour appeler la méthode roll() de myFirstDice, saisissez myFirstDice.roll() (prononcé "myFirstDice-dot-roll()").
myFirstDice.roll()

Votre code, une fois terminé, doit se présenter comme suit.

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. Exécutez votre code ! Vous devriez obtenir le résultat aléatoire d'un lancer de dés sous le nombre de côtés. Exécutez votre code plusieurs fois. Notez que le nombre de côtés reste le même, mais que la valeur du résultat change.
6
4

Félicitations ! Vous avez défini une classe Dice avec une variable sides et une fonction roll(). Dans la fonction main(), vous avez créé une instance d'objet Dice, puis vous avez appelé la méthode roll() pour générer un nombre aléatoire.

4. Renvoyer la valeur de votre lancer de dé

Vous affichez actuellement la valeur de randomNumber dans votre fonction roll(), ce qui fonctionne très bien. Cependant, il est parfois plus utile de renvoyer le résultat d'une fonction vers l'élément qui l'a appelée. Par exemple, vous pouvez affecter le résultat de la méthode roll() à une variable, puis faire avancer un joueur en fonction de ce montant. Voyons comment procéder.

  1. Dans main(), modifiez la ligne myFirstDice.roll(). Créez une val appelée diceRoll. Définissez-la sur la valeur renvoyée par la méthode roll().
val diceRoll = myFirstDice.roll()

Cette opération n'a aucun résultat à ce stade, car roll() ne renvoie rien pour l'instant. Pour que ce code fonctionne comme prévu, roll() doit renvoyer un élément.

Dans les précédents ateliers de programmation, vous avez appris que vous devez spécifier un type de données pour les arguments d'entrée des fonctions. De même, vous devez spécifier un type pour les données renvoyées par une fonction.

  1. Modifiez la fonction roll() pour spécifier le type de données à renvoyer. Dans ce cas, le nombre aléatoire est un entier (Int). Le type renvoyé est donc Int. La syntaxe permettant de spécifier le type renvoyé est la suivante : après le nom de la fonction, après les parenthèses, ajoutez un signe deux-points, un espace, puis le mot clé Int comme type de retour de la fonction. La définition de votre fonction doit ressembler à l'exemple de code ci-dessous.
fun roll(): Int {
  1. Exécutez ce code. Une erreur s'affiche dans la Problems View (Vue des problèmes). Le message indique :
A ‘return' expression required in a function with a block body (‘{...}')

Vous avez modifié la définition de la fonction pour qu'elle renvoie Int, mais le système rapporte que votre code ne renvoie pas un nombre Int. Les éléments "block body" ou "function body" (corps du bloc, ou de la fonction) désignent le code placé entre les accolades d'une fonction. Vous pouvez corriger cette erreur en renvoyant une valeur à partir d'une fonction à l'aide d'une instruction return à la fin du corps de la fonction.

  1. Dans roll(), supprimez l'instruction println() et remplacez-la par une instruction return pour randomNumber. Votre fonction roll() devrait ressembler au code ci-dessous.
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. Dans main(), supprimez l'instruction d'impression des faces du dé.
  2. Ajoutez une instruction pour afficher la valeur de sides et de diceRoll dans une phrase informative. Votre fonction main() terminée devrait ressembler au code ci-dessous.
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. Exécutez votre code pour obtenir le résultat suivant :
Your 6 sided dice rolled 4!

Voici l'ensemble de votre code à ce stade.

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. Modifier le nombre de faces de votre dé

Tous les dés n'ont pas six faces ! Il existe toutes sortes des dés : 4, 8 et même jusqu'à 120 faces !

  1. Dans la méthode roll() de votre classe Dice, modifiez la plage 1..6 codée en dur pour utiliser sides à sa place. Ainsi, la plage (et donc, le nombre aléatoire généré), correspondra toujours au nombre de faces.
val randomNumber = (1..sides).random()
  1. Dans la fonction main(), en dessous et après l'impression du résultat du dé, remplacez la valeur sides de myFirstDice par 20.
myFirstDice.sides = 20
  1. Copiez et collez l'instruction d'impression ci-dessous après avoir modifié le nombre de côtés.
  2. Remplacez l'impression de diceRoll par le résultat de l'appel de la méthode roll() sur myFirstDice.
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")

Votre programme doit se présenter comme suit :

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. Exécutez votre programme. Vous devriez voir un message pour le dé à 6 faces et un autre pour le dé à 20 faces.
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

6. Personnaliser vos dés

Le rôle d'une classe est de représenter une chose, souvent un objet physique du monde réel. Dans le cas présent, la classe Dice représente un dé physique. Dans le monde réel, le nombre de faces d'un dé ne change pas. Si vous voulez un nombre différent de faces, vous devez utiliser un autre dé. En programmation, cela signifie qu'au lieu de modifier la propriété des faces d'une instance d'objet Dice existante, vous devez créer une instance d'objet "dé" avec le nombre de faces dont vous avez besoin.

Dans cette tâche, vous allez modifier la classe Dice afin de spécifier le nombre de faces lorsque vous créez une instance. Modifiez la définition de la classe Dice afin de fournir le nombre de faces. Cette opération ressemble à la manière dont une fonction peut accepter des arguments en entrée.

  1. Modifiez la définition de la classe Dice afin d'accepter un entier appelé numSides. Le code interne de votre classe ne change pas.
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dans la classe Dice, supprimez la variable sides, puisque vous utiliserez désormais numSides.
  2. Corrigez également la plage de sorte à utiliser numSides.

Votre classe Dice devrait se présenter comme suit :

class Dice (val numSides: Int) {

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

Si vous exécutez ce code, vous verrez beaucoup d'erreurs, car vous devez mettre à jour main() pour prendre en compte les modifications apportées à la classe Dice.

  1. Dans main(), pour créer myFirstDice avec six faces, vous devez désormais indiquer le nombre de côtés en tant qu'argument pour la classe Dice, comme indiqué ci-dessous.
    val myFirstDice = Dice(6)
  1. Dans l'instruction d'impression, remplacez sides par numSides.
  2. En dessous, supprimez le code qui fait passer sides à 20, car cette variable n'existe plus.
  3. Supprimez également l'instruction println située en dessous.

Votre fonction main() devrait ressembler à l'exemple de code ci-dessous, et ne générer aucune erreur lorsque vous l'exécutez.

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. Après avoir imprimé le premier lancer de dé, ajoutez du code pour créer et imprimer un second objet Dice appelé mySecondDice, avec 20 faces.
val mySecondDice = Dice(20)
  1. Ajoutez une instruction d'impression qui génère et affiche la valeur renvoyée.
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. Votre fonction main() terminée devrait se présenter comme suit.
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. Exécutez votre programme terminé. Vous devriez obtenir le résultat suivant :
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

7. Adopter les bonnes pratiques de codage

En programmation, la concision est une vertu. Vous pouvez vous débarrasser de la variable randomNumber et renvoyer directement le nombre aléatoire.

  1. Modifiez l'instruction return de sorte à renvoyer directement le nombre aléatoire.
fun roll(): Int {
    return (1..numSides).random()
}

Dans la deuxième instruction d'impression, placez l'appel afin d'obtenir le nombre aléatoire dans le modèle de chaîne. Vous pouvez vous débarrasser de la variable diceRoll en procédant de la même manière dans la première instruction d'impression.

  1. Appelez myFirstDice.roll() dans le modèle de chaîne, puis supprimez la variable diceRoll. Les deux premières lignes de votre code main() se présentent maintenant comme suit.
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. Exécutez votre code. Le résultat ne devrait pas être différent.

Vous faites la même chose lorsque vous refactorisez votre code.

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. Code de solution

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. Résumé

  • Vous avez appelé la fonction random() sur une plage IntRange pour générer un nombre aléatoire : (1..6).random()
  • Les classes sont comme le schéma de construction d'un objet. Elles peuvent avoir des propriétés et des comportements, qui sont implémentés en tant que variables et fonctions.
  • Une instance de classe représente un objet, souvent un objet physique, tel qu'un dé. Vous pouvez appeler les actions sur l'objet et en modifier les attributs.
  • Vous pouvez fournir des valeurs à une classe lorsque vous créez une instance. Par exemple : class Dice(val numSides: Int), puis créer une instance avec Dice(6).
  • Les fonctions peuvent renvoyer un élément. Spécifiez le type de données à renvoyer dans la définition de la fonction, et utilisez une instruction return dans le corps de la fonction ("function body") pour renvoyer un élément. Exemple : fun example(): Int { return 5 }

10. En savoir plus

11. Pour s'entraîner

Action à effectuer :

  • Donnez à votre classe Dice un autre attribut de couleur et créez plusieurs instances de dé avec un nombre différent de faces et différentes couleurs.
  • Créez une classe Coin (pièce), donnez-lui la possibilité de tomber sur l'un ou l'autre de ses côtés, créez une instance de la classe, puis lancez votre pièce ! Comment utiliseriez-vous la fonction random() avec une plage pour tirer à pile ou face ?