Cet atelier de programmation fait partie du cours Kotlin Bootcamp for Programmers (Formation Kotlin pour les programmeurs). Vous tirerez le meilleur parti de ce cours si vous suivez les ateliers de programmation dans l'ordre. En fonction de vos connaissances, vous pouvez parcourir certaines sections. Ce cours s'adresse aux programmeurs qui connaissent un langage orienté objet et souhaitent apprendre l'Kotlin.
Présentation
Dans cet atelier de programmation, vous allez créer un programme Kotlin et en apprendre davantage sur les classes et les objets en langage Kotlin. Vous connaîtrez une grande partie de ce contenu si vous connaissez un autre langage orienté objets, mais Kotlin présente des différences importantes permettant de réduire la quantité de code à écrire. Vous découvrirez aussi les classes abstraites et la délégation d'interface.
Plutôt que de créer un exemple d'application, les leçons de ce cours sont conçues pour développer vos connaissances, mais elles ne doivent pas être indépendantes les unes des autres pour vous permettre de parcourir les sections que vous connaissez déjà. Pour les assembler, de nombreux exemples utilisent un thème d'aquarium. Et si vous voulez voir l'histoire complète de l'aquarium, consultez le cours Udacity Kotlin Bootcamp for Programmers.
Ce que vous devez déjà savoir
- Principes de base de Kotlin, y compris les types, les opérateurs et la boucle
- Syntaxe de la fonction Kotlin
- Principes de base de la programmation orientée objets
- Principes de base d'un IDE, comme IntelliJ IDEA ou Android Studio
Points abordés
- Créer des classes et accéder à des propriétés dans Kotlin
- Créer et utiliser des constructeurs de classes en langage Kotlin
- Créer une sous-classe et comment l'héritage fonctionne
- À propos des classes abstraites, des interfaces et de la délégation d'interface
- Créer et utiliser des classes de données
- Utiliser des singletons, des énumérations et des classes scellées
Objectifs de l'atelier
- Créer une classe avec des propriétés
- Créer un constructeur pour une classe
- Créer une sous-classe
- Examiner des exemples de classes abstraites et d'interfaces
- Créer une classe de données simple
- En savoir plus sur les singletons, les énumérations et les classes scellées
Vous devez déjà connaître les termes de programmation suivants:
- Les classes sont des plans des objets. Par exemple, une classe
Aquarium
correspond au plan de création d'un objet d'aquarium. - Les objets sont des instances de classes. Un objet aquarium est un véritable
Aquarium
. - Les propriétés sont des caractéristiques de classes, telles que la longueur, la largeur et la hauteur d'une
Aquarium
. - Les méthodes, également appelées fonctions de membre, font partie des fonctionnalités de la classe. Les méthodes sont ce que vous pouvez faire avec l'objet. Par exemple, vous pouvez
fillWithWater()
un objetAquarium
. - Une interface est une spécification qu'une classe peut implémenter. Par exemple, le nettoyage est courant pour les objets autres que les aquariums. Le nettoyage est généralement similaire pour différents objets. Vous pouvez donc avoir une interface appelée
Clean
qui définit une méthodeclean()
. La classeAquarium
peut implémenter l'interfaceClean
pour nettoyer l'aquarium avec une éponge douce. - Les packages permettent de regrouper du code associé afin de l'organiser ou de créer une bibliothèque de code. Une fois qu'un package a été créé, vous pouvez en importer le contenu dans un autre fichier et réutiliser le code et les classes qui s'y trouvent.
Dans cette tâche, vous allez créer un package et une classe comportant des propriétés et une méthode.
Étape 1: Créez un package
Les packages vous aident à organiser votre code.
- Dans le volet Project (Projet), sous le projet Hello Kotlin, effectuez un clic droit sur le dossier src.
- Sélectionnez New > Package (Nouveau > ; package) et nommez-le
example.myapp
.
Étape 2: Créer une classe avec des propriétés
Les classes sont définies avec le mot clé class
, et les noms de classe par convention commencent par une lettre majuscule.
- Effectuez un clic droit sur le package example.myapp.
- Sélectionnez New > Kotlin File / Class (Nouveau fichier/Classe Kotlin).
- Sous Kind (Genre), sélectionnez Class (Classe), puis nommez la classe
Aquarium
. IntelliJ IDEA inclut le nom du package dans le fichier et crée une classeAquarium
vide pour vous. - Dans la classe
Aquarium
, définissez et initialisez les propriétésvar
pour la largeur, la hauteur et la longueur (en centimètres). Initialisez les propriétés avec les valeurs par défaut.
package example.myapp
class Aquarium {
var width: Int = 20
var height: Int = 40
var length: Int = 100
}
En arrière-plan, Kotlin crée automatiquement des getters et des setters pour les propriétés que vous avez définies dans la classe Aquarium
. Vous pouvez ainsi accéder directement aux propriétés, par exemple, myAquarium.length
.
Étape 3: Créez une fonction "main()"
Créez un fichier appelé main.kt
pour contenir la fonction main()
.
- Dans le volet Project (Projet) de gauche, faites un clic droit sur le package example.myapp.
- Sélectionnez New > Kotlin File / Class (Nouveau fichier/Classe Kotlin).
- Dans le menu déroulant Kind (Genre), conservez la sélection sous File (Fichier) et nommez le fichier
main.kt
. IntelliJ IDEA inclut le nom du package, mais n'inclut pas la définition de classe d'un fichier. - Définissez une fonction
buildAquarium()
et, dans ce dernier, créez une instance deAquarium
. Pour créer une instance, référencez la classe comme s'il s'agissait d'une fonctionAquarium()
. Cela appelle le constructeur de la classe et crée une instance de la classeAquarium
, comme pour l'utilisation denew
dans d'autres langages. - Définissez une fonction
main()
et appelezbuildAquarium()
.
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium()
}
fun main() {
buildAquarium()
}
Étape 4: Ajoutez une méthode
- Dans la classe
Aquarium
, ajoutez une méthode pour imprimer les propriétés de la dimension "aquarium".
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
}
- Dans
main.kt
, dansbuildAquarium()
, appelez la méthodeprintSize()
surmyAquarium
.
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
}
- Exécutez votre programme en cliquant sur le triangle vert à côté de la fonction
main()
. Observez le résultat.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm
- Dans
buildAquarium()
, ajoutez du code pour définir la hauteur sur 60 et imprimez les propriétés de la dimension modifiée.
fun buildAquarium() {
val myAquarium = Aquarium()
myAquarium.printSize()
myAquarium.height = 60
myAquarium.printSize()
}
- Exécutez votre programme et observez le résultat.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 100 cm Height: 60 cm
Dans cette tâche, vous allez créer un constructeur pour la classe et continuer à utiliser des propriétés.
Étape 1: Créez un constructeur
Au cours de cette étape, vous allez ajouter un constructeur à la classe Aquarium
que vous avez créée à la première tâche. Dans l'exemple précédent, chaque instance de Aquarium
est créée avec les mêmes dimensions. Vous pouvez modifier les dimensions une fois qu'elles sont créées en définissant les propriétés. Toutefois, il est plus simple de définir la taille appropriée au départ.
Dans certains langages de programmation, le constructeur est défini en créant une méthode portant le même nom que la classe. En langage Kotlin, vous définissez le constructeur directement dans la déclaration de la classe, en spécifiant les paramètres entre parenthèses comme si la classe était une méthode. Comme avec les fonctions en Kotlin, ces paramètres peuvent inclure des valeurs par défaut.
- Dans la classe
Aquarium
que vous avez créée précédemment, modifiez la définition de la classe pour inclure trois paramètres constructeur avec des valeurs par défaut pourlength
,width
etheight
, puis attribuez-les aux propriétés correspondantes.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
// Dimensions in cm
var length: Int = length
var width: Int = width
var height: Int = height
...
}
- La solution Kotlin plus compacte consiste à définir les propriétés directement avec le constructeur, à l'aide de
var
ou deval
. De plus, Kotlin crée automatiquement des getters et des setters. Vous pouvez ensuite supprimer les définitions de propriétés dans le corps de la classe.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
- Lorsque vous créez un objet
Aquarium
avec ce constructeur, vous pouvez ne spécifier aucun argument et obtenir les valeurs par défaut, spécifier certains d'entre eux ou les spécifier tous, puis créer un objetAquarium
de taille personnalisée. Dans la fonctionbuildAquarium()
, essayez différentes manières de créer un objetAquarium
à l'aide de paramètres nommés.
fun buildAquarium() {
val aquarium1 = Aquarium()
aquarium1.printSize()
// default height and length
val aquarium2 = Aquarium(width = 25)
aquarium2.printSize()
// default width
val aquarium3 = Aquarium(height = 35, length = 110)
aquarium3.printSize()
// everything custom
val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
aquarium4.printSize()
}
- Exécutez le programme et observez le résultat.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm Width: 25 cm Length: 100 cm Height: 40 cm Width: 20 cm Length: 110 cm Height: 35 cm Width: 25 cm Length: 110 cm Height: 35 cm
Notez que vous n'avez pas dû surcharger le constructeur et écrire une version différente pour chacun de ces cas (plus quelques autres pour les autres combinaisons). Kotlin crée les éléments nécessaires à partir des valeurs par défaut et des paramètres nommés.
Étape 2: Ajoutez des blocs d'initialisation
Les exemples de constructeurs ci-dessus déclarent simplement des propriétés et leur attribue la valeur d'une expression. Si votre constructeur nécessite plus de code d'initialisation, il peut être placé dans un ou plusieurs blocs init
. Dans cette étape, vous allez ajouter des blocs init
à la classe Aquarium
.
- Dans la classe
Aquarium
, ajoutez un blocinit
pour imprimer l'initialisation de l'objet, et un second bloc pour imprimer le volume en litres.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
init {
println("aquarium initializing")
}
init {
// 1 liter = 1000 cm^3
println("Volume: ${width * length * height / 1000} l")
}
}
- Exécutez le programme et observez le résultat.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm
Notez que les blocs init
sont exécutés dans l'ordre dans lequel ils apparaissent dans la définition de classe et qu'ils sont exécutés lorsque le constructeur est appelé.
Étape 3: Apprenez à utiliser des constructeurs secondaires
Dans cette étape, vous allez découvrir les constructeurs secondaires et en ajouter un à votre classe. Outre un constructeur principal, qui peut comporter un ou plusieurs blocs init
, une classe Kotlin peut également comporter un ou plusieurs constructeurs secondaires pour permettre la surcharge des constructeurs, c'est-à-dire des constructeurs avec différents arguments.
- Dans la classe
Aquarium
, ajoutez un constructeur secondaire qui utilise un certain nombre de poissons comme argument, à l'aide du mot cléconstructor
. Créez une propriétéval
pour le réservoir correspondant au volume calculé de l'aquarium en litres, en fonction du nombre de poissons. Supposons que 2 litres (2 000 cm^3) d'eau par poisson, ainsi qu'un peu plus d'espace pour que l'eau ne se déverse pas.
constructor(numberOfFish: Int) : this() {
// 2,000 cm^3 per fish + extra room so water doesn't spill
val tank = numberOfFish * 2000 * 1.1
}
- Dans le constructeur secondaire, conservez les mêmes valeurs de longueur et de largeur (définies dans le constructeur principal), puis calculez la hauteur nécessaire pour faire du réservoir le volume indiqué.
// calculate the height needed
height = (tank / (length * width)).toInt()
- Dans la fonction
buildAquarium()
, ajoutez un appel pour créer unAquarium
à l'aide de votre nouveau constructeur secondaire. Imprimez la taille et le volume.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
- Exécutez votre programme et observez le résultat.
⇒ aquarium initializing Volume: 80 l Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
Notez que le volume est imprimé deux fois : une fois par le bloc init
dans le constructeur principal avant l'exécution du constructeur secondaire, et une fois par le code dans buildAquarium()
.
Vous pouvez également inclure le mot clé constructor
dans le constructeur principal, mais il n'est pas nécessaire dans la plupart des cas.
Étape 4: Ajoutez un getter de propriété
Dans cette étape, vous allez ajouter un getter de propriété explicite. Kotlin définit automatiquement les getters et les setters lorsque vous définissez des propriétés, mais parfois la valeur d'une propriété doit être ajustée ou calculée. Par exemple, ci-dessus, vous avez imprimé le volume de Aquarium
. Vous pouvez rendre le volume disponible en tant que propriété en définissant une variable et un getter pour celle-ci. Étant donné que volume
doit être calculé, la méthode "getter" doit renvoyer la valeur calculée, avec une fonction sur une ligne.
- Dans la classe
Aquarium
, définissez une propriétéInt
appeléevolume
ainsi qu'une méthodeget()
qui calcule le volume à la ligne suivante.
val volume: Int
get() = width * height * length / 1000 // 1000 cm^3 = 1 l
- Supprimez le bloc
init
qui affiche le volume. - Supprimez le code de
buildAquarium()
qui affiche le volume. - Dans la méthode
printSize()
, ajoutez une ligne pour imprimer le volume.
fun printSize() {
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm "
)
// 1 l = 1000 cm^3
println("Volume: $volume l")
}
- Exécutez votre programme et observez le résultat.
⇒ aquarium initializing Width: 20 cm Length: 100 cm Height: 31 cm Volume: 62 l
Les dimensions et le volume sont les mêmes qu'auparavant, mais le volume n'est imprimé qu'une fois après l'initialisation complète de l'objet par le constructeur principal et le constructeur secondaire.
Étape 5: Ajoutez un setter de propriété
Au cours de cette étape, vous allez créer un setter de propriété pour le volume.
- Dans la classe
Aquarium
, remplacezvolume
parvar
afin de pouvoir la définir plusieurs fois. - Ajoutez un setter pour la propriété
volume
en ajoutant une méthodeset()
sous le getter, qui recalcule la hauteur en fonction de la quantité d'eau fournie. Par convention, le nom du paramètre setter estvalue
, mais vous pouvez le modifier si vous préférez.
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- Dans
buildAquarium()
, ajoutez du code pour régler le volume de l'aquarium à 70 litres. Imprimez le nouveau format.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
- Exécutez à nouveau votre programme et observez la hauteur et le volume modifiés.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm
Volume: 70 l
Le code ne comporte aucun modificateur de visibilité (par exemple, public
ou private
) pour l'instant. C'est parce que, par défaut, tout en Kotlin est public, ce qui signifie que tout est accessible partout, y compris les classes, les méthodes, les propriétés et les variables de membre.
Dans Kotlin, les classes, les objets, les interfaces, les constructeurs, les fonctions, les propriétés et leurs setters peuvent avoir des modificateurs de visibilité:
public
signifie visible en dehors de la classe. Tout est public par défaut, y compris les variables et méthodes de la classe.internal
signifie qu'il n'est visible que dans ce module. Un module est un ensemble de fichiers Kotlin compilés, par exemple une bibliothèque ou une application.private
signifie qu'il n'est visible que dans cette classe (ou dans le fichier source si vous utilisez des fonctions).protected
est identique àprivate
, mais il sera également visible par toutes les sous-classes.
Pour en savoir plus, consultez la section Modificateurs de visibilité dans la documentation Kotlin.
Variables des membres
Les propriétés d'une classe ou des variables de membre sont public
par défaut. Si vous les définissez avec var
, ils peuvent être modifiés, c'est-à-dire lisibles et accessibles en écriture. Si vous les définissez avec val
, ils sont en lecture seule après l'initialisation.
Si vous souhaitez une propriété que votre code peut lire ou écrire, mais que le code externe ne peut lire que, vous pouvez laisser la propriété et son getter publics, et déclarer le setter privé, comme illustré ci-dessous.
var volume: Int
get() = width * height * length / 1000
private set(value) {
height = (value * 1000) / (width * length)
}
Dans cette tâche, vous allez découvrir le fonctionnement des sous-classes et de l'héritage en langage Kotlin. Elles sont semblables à ce que vous voyez dans d'autres langues, mais il y a quelques différences.
En langage Kotlin, les classes ne peuvent pas être sous-classées. De même, les propriétés et variables de membre ne peuvent pas être remplacées par des sous-classes (mais elles sont accessibles).
Vous devez marquer une classe comme open
pour autoriser sa sous-classification. De même, vous devez marquer les propriétés et les variables de membre comme open
afin de les remplacer dans la sous-classe. Le mot clé open
est obligatoire pour éviter toute divulgation accidentelle d'informations d'implémentation dans l'interface du cours.
Étape 1: Ouvrez le cours de l'aquarium
Dans cette étape, vous allez définir la classe Aquarium
open
afin de la remplacer à l'étape suivante.
- Marquez la classe
Aquarium
et toutes ses propriétés à l'aide du mot cléopen
.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
open var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- Ajoutez une propriété
shape
ouverte avec la valeur"rectangle"
.
open val shape = "rectangle"
- Ajoutez une propriété
water
ouverte avec un getter qui renvoie 90% du volume deAquarium
.
open var water: Double = 0.0
get() = volume * 0.9
- Ajoutez le code à la méthode
printSize()
pour imprimer la forme et la quantité d'eau sous forme de pourcentage du volume.
fun printSize() {
println(shape)
println("Width: $width cm " +
"Length: $length cm " +
"Height: $height cm ")
// 1 l = 1000 cm^3
println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
- Dans
buildAquarium()
, modifiez le code pour créer unAquarium
avecwidth = 25
,length = 25
etheight = 40
.
fun buildAquarium() {
val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
aquarium6.printSize()
}
- Exécutez votre programme et observez le nouveau résultat.
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full)
Étape 2: Créez une sous-classe
- Créez une sous-classe de
Aquarium
appeléeTowerTank
, qui implémente un réservoir cylindrique arrondi au lieu d'un réservoir rectangulaire. Vous pouvez ajouterTowerTank
sousAquarium
, car vous pouvez ajouter une autre classe dans le même fichier que la classeAquarium
. - Dans
TowerTank
, ignorez la propriétéheight
, définie dans le constructeur. Pour ignorer une propriété, utilisez le mot cléoverride
dans la sous-classe.
- Définissez le constructeur de
TowerTank
surdiameter
. Utilisezdiameter
pourlength
etwidth
lorsque vous appelez le constructeur dans la super-classeAquarium
.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
- Ignorez la propriété de volume pour calculer un cylindre. La formule d'un cylindre est Pi multipliée par le carré du rayon multiplié par sa hauteur. Vous devez importer la constante
PI
depuisjava.lang.Math
.
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
- Dans
TowerTank
, remplacez la propriétéwater
par 80% du volume.
override var water = volume * 0.8
- Remplacez
shape
par"cylinder"
.
override val shape = "cylinder"
- Votre classe
TowerTank
finale devrait ressembler à l'exemple de code ci-dessous.
Aquarium.kt
:
package example.myapp
import java.lang.Math.PI
... // existing Aquarium class
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
override var volume: Int
// ellipse area = π * r1 * r2
get() = (width/2 * length/2 * height / 1000 * PI).toInt()
set(value) {
height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
}
override var water = volume * 0.8
override val shape = "cylinder"
}
- Dans
buildAquarium()
, créez unTowerTank
d'un diamètre de 25 cm et d'une hauteur de 45 cm. Imprimez le format.
main.kt:
package example.myapp
fun buildAquarium() {
val myAquarium = Aquarium(width = 25, length = 25, height = 40)
myAquarium.printSize()
val myTower = TowerTank(diameter = 25, height = 40)
myTower.printSize()
}
- Exécutez votre programme et observez le résultat.
⇒ aquarium initializing rectangle Width: 25 cm Length: 25 cm Height: 40 cm Volume: 25 l Water: 22.5 l (90.0% full) aquarium initializing cylinder Width: 25 cm Length: 25 cm Height: 40 cm Volume: 18 l Water: 14.4 l (80.0% full)
Parfois, vous souhaitez définir des propriétés ou des comportements communs à partager entre certaines classes associées. Kotlin propose deux méthodes pour procéder, les interfaces et les classes abstraites. Dans cette tâche, vous allez créer une classe AquariumFish
abstraite pour les propriétés communes à tous les poissons. Vous créez une interface appelée FishAction
pour définir un comportement commun à tous les poissons.
- Ni une classe abstraite, ni une interface ne peuvent être instanciées séparément, ce qui signifie que vous ne pouvez pas créer directement d'objets de ces types.
- Les classes abstraites ont des constructeurs.
- Les interfaces ne peuvent pas avoir de logique de constructeur ni stocker d'état.
Étape 1 : Créer une classe abstraite
- Sous example.myapp, créez un fichier
AquariumFish.kt
. - Créez une classe, également appelée
AquariumFish
, et marquez-la avecabstract
. - Ajoutez une propriété
String
,color
, puis marquez-la avecabstract
.
package example.myapp
abstract class AquariumFish {
abstract val color: String
}
- Créez deux sous-classes de
AquariumFish
,Shark
etPlecostomus
. - La classe
color
étant abstraite, les sous-classes doivent l'implémenter. DéfinissezShark
en gris etPlecostomus
en or.
class Shark: AquariumFish() {
override val color = "gray"
}
class Plecostomus: AquariumFish() {
override val color = "gold"
}
- Dans main.kt, créez une fonction
makeFish()
pour tester vos classes. Instanciez unShark
et unPlecostomus
, puis imprimez leur couleur. - Supprimez votre code de test précédent dans
main()
et ajoutez un appel àmakeFish()
. Le code doit ressembler à ceci :
main.kt
:
package example.myapp
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
println("Plecostomus: ${pleco.color}")
}
fun main () {
makeFish()
}
- Exécutez votre programme et observez le résultat.
⇒ Shark: gray Plecostomus: gold
Le schéma suivant représente les classes Shark
et Plecostomus
, qui sous-classe de la classe abstraite AquariumFish
.
Étape 2. Créer une interface
- Dans AquariumFish.kt, créez une interface nommée
FishAction
avec la méthodeeat()
.
interface FishAction {
fun eat()
}
- Ajoutez
FishAction
à chacune des sous-classes et implémentezeat()
en indiquant ce qu'il fait.
class Shark: AquariumFish(), FishAction {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
class Plecostomus: AquariumFish(), FishAction {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- Dans la fonction
makeFish()
, demandez à chaque poisson que vous avez créé de manger quelque chose en appelanteat()
.
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
shark.eat()
println("Plecostomus: ${pleco.color}")
pleco.eat()
}
- Exécutez votre programme et observez le résultat.
⇒ Shark: gray hunt and eat fish Plecostomus: gold eat algae
Le schéma suivant représente la classe Shark
et la classe Plecostomus
, qui sont toutes les deux composées et implémentent l'interface FishAction
.
Quand utiliser des classes abstraites ou des interfaces ?
Les exemples ci-dessus sont simples. Cependant, quand vous disposez de nombreuses classes liées, des classes abstraites et des interfaces peuvent vous aider à garder votre conception plus propre, plus organisée et plus facile à gérer.
Comme indiqué ci-dessus, les classes abstraites peuvent avoir des constructeurs, mais pas les interfaces. En revanche, elles sont très similaires. Alors, quand les utiliser ?
Lorsque vous utilisez des interfaces pour composer une classe, la fonctionnalité de la classe est étendue à l'aide des instances qu'elle contient. La composition a tendance à faciliter la réutilisation et le raisonnement du code plutôt que l'héritage d'une classe abstraite. Vous pouvez également utiliser plusieurs interfaces dans une classe, mais une seule sous-classe d'une classe abstraite.
La composition s'accompagne souvent d'une meilleure encapsulation, d'une coupulation plus faible (interdépendance), d'interfaces plus propres et d'un code plus facile à utiliser. C'est pourquoi il est préférable d'utiliser la composition avec des interfaces. En revanche, l'héritage d'une classe abstraite est généralement adapté à certains problèmes. Vous devez donc privilégier la composition, mais lorsque l'héritage est logique, Kotlin vous le permet également.
- Utilisez une interface si vous disposez de nombreuses méthodes et d'une ou deux implémentations par défaut, comme dans
AquariumAction
ci-dessous.
interface AquariumAction {
fun eat()
fun jump()
fun clean()
fun catchFish()
fun swim() {
println("swim")
}
}
- Utilisez une classe abstraite chaque fois que vous ne pouvez pas terminer une classe. Par exemple, si vous revenez à la classe
AquariumFish
, vous pouvez appliquer laFishAction
à toutes lesAquariumFish
et fournir une implémentation par défaut poureat
sans laissercolor
abstrait, car il n'y a pas vraiment de couleur par défaut pour le poisson.
interface FishAction {
fun eat()
}
abstract class AquariumFish: FishAction {
abstract val color: String
override fun eat() = println("yum")
}
La tâche précédente a introduit des classes abstraites, des interfaces et l'idée de la composition. La délégation d'interface est une technique avancée dans laquelle les méthodes d'une interface sont mises en œuvre par un objet d'aide (ou délégué). Il est ensuite utilisé par une classe. Cette technique peut s'avérer utile lorsque vous utilisez une interface dans une série de classes sans lien direct, avec la possibilité d'ajouter la fonctionnalité d'interface nécessaire à une classe d'assistance distincte. Chacune de ces classes utilise une instance de la classe d'assistance pour implémenter la fonctionnalité.
Dans cette tâche, vous allez ajouter des fonctionnalités à une classe à l'aide de la délégation d'interface.
Étape 1: Créer une interface
- Dans AquariumFish.kt, supprimez la classe
AquariumFish
. Au lieu d'hériter de la classeAquariumFish
,Plecostomus
etShark
vont implémenter des interfaces pour l'action de poisson et pour leur couleur. - Créez une interface
FishColor
qui définit la couleur sous forme de chaîne.
interface FishColor {
val color: String
}
- Modifiez
Plecostomus
pour implémenter deux interfaces,FishAction
etFishColor
. Vous devez remplacercolor
deFishColor
eteat()
deFishAction
.
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
- Modifiez votre classe
Shark
pour qu'elle implémente également les deux interfacesFishAction
etFishColor
au lieu d'hériter deAquariumFish
.
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
- Votre code, une fois fini, doit ressembler à ceci:
package example.myapp
interface FishAction {
fun eat()
}
interface FishColor {
val color: String
}
class Plecostomus: FishAction, FishColor {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
class Shark: FishAction, FishColor {
override val color = "gray"
override fun eat() {
println("hunt and eat fish")
}
}
Étape 2: Créer un cours singleton
Vous allez ensuite implémenter la configuration de la partie de délégation en créant une classe d'assistance qui implémente FishColor
. Vous créez une classe de base appelée GoldColor
qui implémente FishColor
. Elle indique simplement que sa couleur est or.
Il n'est pas logique d'utiliser plusieurs instances de GoldColor
, car elles ont toutes le même effet. Kotlin vous permet de déclarer une classe dans laquelle vous ne pouvez créer qu'une seule instance de celui-ci en utilisant le mot clé object
au lieu de class
. Kotlin crée cette instance, et cette instance est référencée par le nom de la classe. Ensuite, tous les autres objets peuvent utiliser cette instance unique : il n'existe aucun moyen de créer d'autres instances de cette classe. Si vous connaissez le modèle singleton, voici comment implémenter des singletons dans Kotlin.
- Dans AquariumFish.kt, créez un objet pour
GoldColor
. Remplacer la couleur.
object GoldColor : FishColor {
override val color = "gold"
}
Étape 3: Ajoutez une délégation d'interface pour FishColor
Vous pouvez à présent utiliser la délégation d'interface.
- Dans AquariumFish.kt, supprimez le forçage de
color
dansPlecostomus
. - Modifiez la classe
Plecostomus
pour obtenir sa couleur à partir deGoldColor
. Pour ce faire, ajoutezby GoldColor
à la déclaration de la classe, ce qui crée la délégation. Cela signifie qu'au lieu d'implémenterFishColor
, utilisez l'implémentation fournie parGoldColor
. Ainsi, chaque fois qu'un utilisateur accède àcolor
, il est délégué àGoldColor
.
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
Avec la classe telle quelle, tous les plis sont dorés, mais ces poissons sont en fait disponibles en plusieurs couleurs. Pour résoudre ce problème, ajoutez un paramètre constructeur pour la couleur avec GoldColor
comme couleur par défaut pour Plecostomus
.
- Modifiez la classe
Plecostomus
pour qu'elle utilisefishColor
dans son constructeur et définissez sa valeur par défaut surGoldColor
. Remplacez la délégation deby GoldColor
parby fishColor
.
class Plecostomus(fishColor: FishColor = GoldColor): FishAction,
FishColor by fishColor {
override fun eat() {
println("eat algae")
}
}
Étape 4: Ajoutez une délégation d'interface pour FishAction
De la même manière, vous pouvez utiliser la délégation d'interface pour FishAction
.
- Dans AquariumFish.kt, créez une classe
PrintingFishAction
qui implémenteFishAction
, qui prend unString
, unfood
, puis affiche ce que mange le poisson.
class PrintingFishAction(val food: String) : FishAction {
override fun eat() {
println(food)
}
}
- Dans la classe
Plecostomus
, supprimez la fonction de remplacementeat()
, car vous la remplacerez par une délégation. - Dans la déclaration de
Plecostomus
, déléguezFishAction
àPrintingFishAction
en transmettant"eat algae"
. - Avec cette délégation, il n'y a pas de code dans le corps de la classe
Plecostomus
. Supprimez donc le{}
, car tous les remplacements sont gérés par la délégation d'interface.
class Plecostomus (fishColor: FishColor = GoldColor):
FishAction by PrintingFishAction("eat algae"),
FishColor by fishColor
Le schéma suivant représente les classes Shark
et Plecostomus
, toutes deux composées des interfaces PrintingFishAction
et FishColor
, mais déléguant l'implémentation à ces classes.
La délégation d'interface est puissante, et vous devez généralement réfléchir à la façon de l'utiliser chaque fois que vous pouvez utiliser une classe abstraite dans une autre langue. Il vous permet d'utiliser la composition pour appliquer des comportements plutôt que d'avoir à créer de nombreuses sous-classes, chacune spécialisée dans une méthode différente.
Une classe de données est semblable à un struct
dans d'autres langues (elle contient principalement des données), mais un objet de classe de données est toujours un objet. Les objets de classe de données Kotlin présentent certains avantages supplémentaires, comme des utilitaires d'impression et de copie. Dans cette tâche, vous allez créer une classe de données simple et en apprendre davantage sur la compatibilité de Kotlin avec les classes de données.
Étape 1: Créer une classe de données
- Ajoutez un nouveau package
decor
sous le package example.myapp pour contenir le nouveau code. Effectuez un clic droit sur example.myapp dans le volet Projet, puis sélectionnez Fichier > Nouveau > Package. - Dans le package, créez une classe appelée
Decoration
.
package example.myapp.decor
class Decoration {
}
- Pour faire de
Decoration
une classe de données, ajoutez le mot clédata
à la déclaration de classe. - Ajoutez une propriété
String
appeléerocks
pour fournir à la classe des données.
data class Decoration(val rocks: String) {
}
- Dans le fichier, en dehors de la classe, ajoutez une fonction
makeDecorations()
pour créer et imprimer une instance deDecoration
avec"granite"
.
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
}
- Ajoutez une fonction
main()
pour appelermakeDecorations()
et exécutez votre programme. Comme vous pouvez le constater, cette sortie est logique, car il s'agit d'une classe de données.
⇒ Decoration(rocks=granite)
- Dans
makeDecorations()
, instanciez deux autres objetsDecoration
qui sont tous deux &slate, et imprimez-les.
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
val decoration2 = Decoration("slate")
println(decoration2)
val decoration3 = Decoration("slate")
println(decoration3)
}
- Dans
makeDecorations()
, ajoutez une instruction d'impression qui affiche le résultat de la comparaison dedecoration1
avecdecoration2
, et une seconde comparantdecoration3
avecdecoration2
. Utilisez la méthode égals() fournie par les classes de données.
println (decoration1.equals(decoration2))
println (decoration3.equals(decoration2))
- Exécutez votre code.
⇒ Decoration(rocks=granite) Decoration(rocks=slate) Decoration(rocks=slate) false true
Étape 2. Utiliser la déstructuration
Pour obtenir les propriétés d'un objet de données et les attribuer à des variables, vous pouvez les attribuer un par un, comme ceci.
val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver
À la place, vous pouvez créer des variables, une pour chaque propriété, et attribuer l'objet de données au groupe de variables. Kotlin place la valeur de la propriété dans chaque variable.
val (rock, wood, diver) = decoration
C'est ce qu'on appelle la déstructuration. Le nombre de variables doit correspondre au nombre de propriétés. En outre, les variables sont attribuées dans l'ordre dans lequel elles sont déclarées dans la classe. Voici un exemple complet que vous pouvez essayer sur Decoration.kt.
// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}
fun makeDecorations() {
val d5 = Decoration2("crystal", "wood", "diver")
println(d5)
// Assign all properties to variables.
val (rock, wood, diver) = d5
println(rock)
println(wood)
println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver) crystal wood diver
Si vous n'avez pas besoin d'une ou de plusieurs propriétés, vous pouvez les ignorer en utilisant _
au lieu d'un nom de variable, comme indiqué dans le code ci-dessous.
val (rock, _, diver) = d5
Dans cette tâche, vous allez découvrir certaines classes spéciales en langage Kotlin, dont les suivantes:
- Cours de singleton
- Enums
- Cours scellés
Étape 1: Rappeler les classes singleton
Reprenons l'exemple précédent avec la classe GoldColor
.
object GoldColor : FishColor {
override val color = "gold"
}
Comme chaque instance de GoldColor
effectue la même opération, elle est déclarée en tant que object
au lieu de class
en tant que singleton. Il ne peut y en avoir qu'une seule.
Étape 2: création d'une énumération
Kotlin prend également en charge les énumérations, ce qui vous permet d'énumérer des éléments et de les mentionner par nom, comme dans d'autres langages. Déclarez une énumération en ajoutant le mot clé enum
au début de la déclaration. Une déclaration d'énumération de base ne nécessite qu'une liste de noms, mais vous pouvez également définir un ou plusieurs champs associés à chaque nom.
- Dans Decoration.kt, testez un exemple d'énumération.
enum class Color(val rgb: Int) {
RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}
Les énumérations sont un peu comme les singletons : il ne peut y en avoir qu'un et une seule de chaque valeur dans l'énumération. Par exemple, il ne peut y avoir qu'un seul Color.RED
, un Color.GREEN
et un Color.BLUE
. Dans cet exemple, les valeurs RVB sont attribuées à la propriété rgb
pour représenter les composants de couleur. Vous pouvez également obtenir la valeur ordinale d'une énumération à l'aide de la propriété ordinal
et son nom à l'aide de la propriété name
.
- Essayez un autre exemple d'énumération.
enum class Direction(val degrees: Int) {
NORTH(0), SOUTH(180), EAST(90), WEST(270)
}
fun main() {
println(Direction.EAST.name)
println(Direction.EAST.ordinal)
println(Direction.EAST.degrees)
}
⇒ EAST 2 90
Étape 3: Créez une classe scellée
Une classe scellée est une classe qui peut être sous-classée, mais uniquement dans le fichier où elle est déclarée. Si vous essayez de sous-classer la classe dans un autre fichier, une erreur s'affiche.
Étant donné que les classes et les sous-classes se trouvent dans le même fichier, Kotlin connaîtra toutes les sous-classes de manière statique. Autrement dit, au moment de la compilation, le compilateur voit toutes les classes et sous-classes, et sait qu'il s'agit de toutes ces classes. Il peut donc effectuer des vérifications supplémentaires pour vous.
- Dans AquariumFish.kt, essayez un exemple de cours scellé, en conservant le thème aquatique.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()
fun matchSeal(seal: Seal): String {
return when(seal) {
is Walrus -> "walrus"
is SeaLion -> "sea lion"
}
}
La classe Seal
ne peut pas être sous-classée dans un autre fichier. Si vous souhaitez ajouter d'autres types Seal
, vous devez les ajouter dans le même fichier. Les classes scellées constituent ainsi un moyen sûr de représenter un nombre fixe de types. Par exemple, les classes scellées sont idéales pour renvoyer une réussite ou une erreur à partir d'une API réseau.
Cette leçon a abordé de nombreux points. Bien qu'il soit courant de se familiariser avec d'autres langages de programmation orientés objets, Kotlin ajoute quelques fonctionnalités pour que le code soit concis et lisible.
Classes et constructeurs
- Définissez une classe en langage Kotlin à l'aide de
class
. - Kotlin crée automatiquement des setters et des getters pour les propriétés.
- Définissez le constructeur principal directement dans la définition de classe. Exemples :
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
- Si un constructeur principal nécessite du code supplémentaire, écrivez-le dans un ou plusieurs blocs
init
. - Une classe peut définir un ou plusieurs constructeurs secondaires à l'aide de
constructor
, mais le style Kotlin utilise plutôt une fonction de fabrique.
Modificateurs de visibilité et sous-classes
- Toutes les classes et les fonctions en langage Kotlin sont
public
par défaut, mais vous pouvez utiliser des modificateurs pour remplacer la visibilité parinternal
,private
ouprotected
. - Pour créer une sous-classe, la classe parente doit être marquée comme
open
. - Pour remplacer des méthodes et des propriétés dans une sous-classe, les méthodes et les propriétés doivent être marquées comme
open
dans la classe parente. - Une classe scellée ne peut être sous-classée que dans le fichier dans lequel elle est définie. Créez une classe scellée en préfixe de la déclaration avec
sealed
.
Classes de données, singletons et énumérations
- Créez une classe de données en ajoutant la déclaration avec le préfixe
data
. - La déstructuration est un raccourci permettant d'attribuer les propriétés d'un objet
data
à des variables distinctes. - Créez une classe Singleton en utilisant
object
au lieu declass
. - Définissez une énumération à l'aide de
enum class
.
Classes abstraites, interfaces et délégation
- Les classes abstraites et les interfaces constituent deux façons de partager un comportement commun entre les classes.
- Une classe abstraite définit les propriétés et le comportement, mais quitte l'implémentation pour les sous-classes.
- Une interface définit le comportement et peut fournir des implémentations par défaut pour tout ou partie du comportement.
- Lorsque vous utilisez des interfaces pour composer une classe, la fonctionnalité de la classe est étendue à l'aide des instances qu'elle contient.
- La délégation d'interface utilise la composition, mais délègue également l'implémentation aux classes d'interface.
- La composition est un moyen efficace d'ajouter des fonctionnalités à une classe à l'aide de la délégation d'interface. La composition générale est préférable, mais l'héritage d'une classe abstraite est mieux adapté à certains problèmes.
Documentation Kotlin
Si vous souhaitez en savoir plus sur un sujet de ce cours ou si vous rencontrez des difficultés, https://kotlinlang.org constitue le meilleur point de départ.
- Cours et héritage
- Constructeurs
- Fonctions d'usine
- Propriétés et champs
- Modificateurs de visibilité
- Classes abstraites
- Interfaces
- Délégation
- Classes de données
- Égalité
- Déstructuration
- Déclarations d'objets
- Classes d'énumération
- Cours scellés
- Gérer les erreurs facultatives à l'aide de classes scellées Kotlin
Tutoriels Kotlin
Le site Web https://try.kotlinlang.org propose des tutoriels enrichis appelés Kotlin Koans, un interpréteur Web ainsi qu'une documentation de référence complète avec des exemples.
Cours Udacity
Pour afficher le cours Udacity sur ce sujet, consultez Kotlin Bootcamp for Programmers (Formation Kotlin pour les programmeurs).
IntelliJ IDEA
Consultez la documentation sur IntelliJ IDEA sur le site Web de JetBrains.
Cette section répertorie les devoirs possibles pour les élèves qui effectuent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. C'est à l'enseignant de procéder comme suit:
- Si nécessaire, rendez-les.
- Communiquez aux élèves comment rendre leurs devoirs.
- Notez les devoirs.
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ils n'ont pas besoin d'attribuer les devoirs de leur choix.
Si vous suivez vous-même cet atelier de programmation, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondez à ces questions.
Question 1
Les classes ont une méthode spéciale qui sert de plan pour créer des objets de cette classe. Comment s'appelle cette méthode ?
▢ Un compilateur
▢ Instanciateur
▢ Un constructeur
▢ Un plan
Question 2
Parmi les affirmations suivantes concernant les interfaces et les classes abstraites, laquelle n'est PAS correcte ?
▢ Des classes abstraites peuvent avoir des constructeurs.
▢ Des interfaces ne peuvent pas comporter de constructeurs.
▢ Les interfaces et les classes abstraites peuvent être instanciées directement.
▢ Les propriétés abstraites doivent être implémentées par des sous-classes de la classe abstraite.
Question 3
Parmi les propositions suivantes, laquelle n'est PAS un modificateur de visibilité Kotlin pour les propriétés, les méthodes, etc.?
▢ internal
▢ nosubclass
▢ protected
▢ private
Question 4
Tenez compte de la classe de données suivante:data class Fish(val name: String, val species:String, val colors:String)
Quel élément N'EST PAS un code valide pour créer et déstructurer un objet Fish
?
▢ val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")
▢ val (name2, _, colors2) = Fish("Bitey", "shark", "gray")
▢ val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")
▢ val (name4, species4, colors4) = Fish("Harry", "halibut")
Question 5
Imaginons que vous soyez propriétaire d'un zoo avec de nombreux animaux dont tous doivent prendre soin. Parmi les propositions suivantes, laquelle ne correspond PAS à une mise en œuvre d'une prestation de soins ?
▢ interface
pour différents types d'aliments consommés par les animaux.
▢ Une classe abstract Caretaker
à partir de laquelle vous pouvez créer différents types d'employés.
▢ interface
pour fournir de l'eau propre à un animal.
▢ Une classe data
pour une entrée dans une planification d'alimentation.
Passez à la leçon suivante :
Pour une présentation du cours, y compris des liens vers d'autres ateliers de programmation, consultez le "Kotlin Bootcamp for Programmers: Welcome to the course."