Cet atelier de programmation fait partie du cours Kotlin Bootcamp for Programmers. Vous tirerez pleinement parti de ce cours en suivant les ateliers de programmation dans l'ordre. En fonction de vos connaissances, vous pourrez peut-être survoler certaines sections. Ce cours s'adresse aux programmeurs qui connaissent un langage orienté objet et qui souhaitent apprendre Kotlin.
Introduction
Dans cet atelier de programmation, vous allez créer un programme Kotlin et découvrir les classes et les objets dans ce langage. Une grande partie de ce contenu vous sera familière si vous connaissez un autre langage orienté objet, mais Kotlin présente des différences importantes pour réduire la quantité de code que vous devez écrire. Vous découvrirez également les classes abstraites et la délégation d'interface.
Plutôt que de créer une seule application exemple, les leçons de ce cours sont conçues pour développer vos connaissances, mais sont semi-indépendantes les unes des autres afin que vous puissiez parcourir les sections que vous connaissez. Pour les relier, de nombreux exemples utilisent un thème d'aquarium. Si vous souhaitez en savoir plus sur l'histoire de l'aquarium, consultez le cours Udacity Kotlin Bootcamp for Programmers.
Ce que vous devez déjà savoir
- Les bases de Kotlin, y compris les types, les opérateurs et les boucles
- Syntaxe des fonctions Kotlin
- Principes de base de la programmation orientée objet
- Les bases d'un IDE tel qu'IntelliJ IDEA ou Android Studio
Points abordés
- Créer des classes et accéder à des propriétés en Kotlin
- Créer et utiliser des constructeurs de classe en Kotlin
- Créer une sous-classe et comprendre le fonctionnement de l'héritage
- À 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 pour les objets. Par exemple, une classe
Aquarium
est le plan permettant de créer un objet aquarium. - Les objets sont des instances de classes. Un objet aquarium est un
Aquarium
réel. - Les propriétés sont des caractéristiques des classes, telles que la longueur, la largeur et la hauteur d'un
Aquarium
. - Les méthodes, également appelées fonctions membres, sont les fonctionnalités de la classe. Les méthodes correspondent à 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 commun à d'autres objets que les aquariums, et il se fait généralement de manière 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é pour l'organiser ou créer une bibliothèque de code. Une fois un package créé, vous pouvez importer son contenu dans un autre fichier et réutiliser le code et les classes qu'il contient.
Dans cette tâche, vous allez créer un package et une classe avec des propriétés et une méthode.
Étape 1 : Créez un package
Les packages peuvent vous aider à organiser votre code.
- Dans le volet Project (Projet), sous le projet Hello Kotlin, effectuez un clic droit sur le dossier src.
- Sélectionnez Nouveau > Package et nommez-le
example.myapp
.
Étape 2 : Créez une classe avec des propriétés
Les classes sont définies avec le mot clé class
, et les noms de classe commencent par convention par une 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 des 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 donc accéder directement aux propriétés, par exemple myAquarium.length
.
Étape 3 : Créez une fonction main()
Créez un fichier nommé main.kt
pour contenir la fonction main()
.
- Dans le volet Project (Projet) à gauche, effectuez un clic droit sur le package example.myapp.
- Sélectionnez New > Kotlin File/Class (Nouveau > Fichier/Classe Kotlin).
- Dans le menu déroulant Type, conservez la sélection Fichier et nommez le fichier
main.kt
. IntelliJ IDEA inclut le nom du package, mais pas la définition de classe pour un fichier. - Définissez une fonction
buildAquarium()
et créez une instance deAquarium
à l'intérieur. Pour créer une instance, référencez la classe comme s'il s'agissait d'une fonction,Aquarium()
. Cela appelle le constructeur de la classe et crée une instance de la classeAquarium
, comme 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 dimension de l'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 imprimer les propriétés de dimension modifiées.
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 à travailler avec les propriétés.
Étape 1 : Créer un constructeur
Dans cette étape, vous allez ajouter un constructeur à la classe Aquarium
que vous avez créée dans 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 la dimension créée en définissant les propriétés, mais il serait plus simple de la créer directement à la bonne taille.
Dans certains langages de programmation, le constructeur est défini en créant une méthode dans la classe qui porte le même nom que la classe. En Kotlin, vous définissez le constructeur directement dans la déclaration de classe elle-même, en spécifiant les paramètres entre parenthèses comme si la classe était une méthode. Comme pour 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 de constructeur avec des valeurs par défaut pourlength
,width
etheight
, et 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 méthode Kotlin plus compacte consiste à définir les propriétés directement avec le constructeur, à l'aide de
var
ouval
. Kotlin crée également automatiquement les getters et les 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, ou n'en spécifier que certains, ou encore tous les spécifier et créer unAquarium
de taille entièrement personnalisée. Dans la fonctionbuildAquarium()
, essayez différentes façons 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 eu à surcharger le constructeur ni à écrire une version différente pour chacun de ces cas (plus quelques autres pour les autres combinaisons). Kotlin crée ce qui est nécessaire à partir des valeurs par défaut et des paramètres nommés.
Étape 2 : Ajoutez des blocs d'initialisation
Les constructeurs d'exemple ci-dessus ne font que déclarer des propriétés et leur attribuer la valeur d'une expression. Si votre constructeur a besoin de 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 indiquer que l'objet est en cours d'initialisation, 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 la classe, et qu'ils sont tous exécutés lorsque le constructeur est appelé.
Étape 3 : En savoir plus sur les constructeurs secondaires
Dans cette étape, vous allez découvrir les constructeurs secondaires et en ajouter un à votre classe. En plus d'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 de constructeur, c'est-à-dire des constructeurs avec des arguments différents.
- Dans la classe
Aquarium
, ajoutez un constructeur secondaire qui prend un nombre de poissons comme argument, à l'aide du mot cléconstructor
. Créez une propriété de réservoirval
pour le volume calculé de l'aquarium en litres en fonction du nombre de poissons. Comptez 2 litres (2 000 cm³) d'eau par poisson, plus un peu d'espace supplémentaire pour éviter que l'eau ne déborde.
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 la longueur et la largeur (qui ont été définies dans le constructeur principal), et calculez la hauteur nécessaire pour que le réservoir ait 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 auriez pu inclure le mot clé constructor
dans le constructeur principal, mais ce n'est pas nécessaire dans la plupart des cas.
Étape 4 : Ajouter un getter de propriété
Au cours de 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 il arrive que la valeur d'une propriété doive ê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 celui-ci. Comme volume
doit être calculé, le getter doit renvoyer la valeur calculée, ce que vous pouvez faire avec une fonction sur une seule ligne.
- Dans la classe
Aquarium
, définissez une propriétéInt
appeléevolume
, puis définissez une méthodeget()
qui calcule le volume sur 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 dans
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'avant, mais le volume n'est imprimé qu'une seule fois après l'initialisation complète de l'objet par le constructeur principal et le constructeur secondaire.
Étape 5 : Ajouter un setter de propriété
Dans cette étape, vous allez créer un setter de propriété pour le volume.
- Dans la classe
Aquarium
, remplacezvolume
parvar
afin qu'il puisse être défini 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 du setter estvalue
, mais vous pouvez le modifier si vous le souhaitez.
var volume: Int
get() = width * height * length / 1000
set(value) {
height = (value * 1000) / (width * length)
}
- Dans
buildAquarium()
, ajoutez du code pour définir le volume de l'aquarium sur 70 litres. Imprimez la nouvelle taille.
fun buildAquarium() {
val aquarium6 = Aquarium(numberOfFish = 29)
aquarium6.printSize()
aquarium6.volume = 70
aquarium6.printSize()
}
- Exécutez à nouveau votre programme et observez la modification de la hauteur et du volume.
⇒ 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
Jusqu'à présent, le code ne contenait aucun modificateur de visibilité, tel que public
ou private
. En effet, par défaut, tout est public dans Kotlin, ce qui signifie que tout est accessible partout, y compris les classes, les méthodes, les propriétés et les variables membres.
En 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 les méthodes de la classe.internal
signifie qu'il ne sera visible que dans ce module. Un module est un ensemble de fichiers Kotlin compilés ensemble, par exemple une bibliothèque ou une application.private
signifie qu'il ne sera visible que dans cette classe (ou dans le fichier source si vous travaillez avec 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 membres
Les propriétés d'une classe, ou variables membres, sont public
par défaut. Si vous les définissez avec var
, elles sont modifiables, c'est-à-dire lisibles et accessibles en écriture. Si vous les définissez avec val
, elles seront en lecture seule après l'initialisation.
Si vous souhaitez qu'une propriété puisse être lue ou écrite par votre code, mais uniquement lue par le code externe, vous pouvez laisser la propriété et son getter comme publics et déclarer le setter comme privé, comme indiqué 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 comment fonctionnent les sous-classes et l'héritage en Kotlin. Elles sont similaires à celles que vous avez vues dans d'autres langues, mais il existe quelques différences.
En Kotlin, par défaut, les classes ne peuvent pas être sous-classées. De même, les propriétés et les variables membres ne peuvent pas être remplacées par des sous-classes (bien qu'elles puissent être accessibles).
Vous devez marquer une classe comme open
pour qu'elle puisse être sous-classée. De même, vous devez marquer les propriétés et les variables membres comme open
pour les remplacer dans la sous-classe. Le mot clé open
est obligatoire pour éviter de divulguer accidentellement des détails d'implémentation dans l'interface de la classe.
Étape 1 : Ouvrez le cours Aquarium
Dans cette étape, vous allez rendre la classe Aquarium
open
afin de pouvoir la remplacer à l'étape suivante.
- Marquez la classe
Aquarium
et toutes ses propriétés avec le 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 du code à la méthode
printSize()
pour imprimer la forme et la quantité d'eau en 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éer 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
, remplacez la propriétéheight
, qui est définie dans le constructeur. Pour remplacer une propriété, utilisez le mot cléoverride
dans la sous-classe.
- Faites en sorte que le constructeur de
TowerTank
prenne undiameter
. Utilisezdiameter
pourlength
etwidth
lorsque vous appelez le constructeur dans la superclasseAquarium
.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
- Remplacez la propriété de volume pour calculer un cylindre. La formule pour un cylindre est pi fois le rayon au carré fois la 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 au 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 la taille.
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 un comportement ou des propriétés communs à partager entre certaines classes associées. Kotlin propose deux façons de le faire : 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 le comportement commun à tous les poissons.
- Ni une classe abstraite ni une interface ne peuvent être instanciées seules, ce qui signifie que vous ne pouvez pas créer d'objets de ces types directement.
- 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
, et marquez-la avecabstract
.
package example.myapp
abstract class AquariumFish {
abstract val color: String
}
- Créez deux sous-classes de
AquariumFish
,Shark
etPlecostomus
. - Comme
color
est abstrait, les sous-classes doivent l'implémenter. DéfinissezShark
sur gris etPlecostomus
sur 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 affichez la couleur de chacun. - Supprimez le code de test précédent dans
main()
et ajoutez un appel àmakeFish()
. Votre code devrait ressembler à l'exemple ci-dessous.
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 sont des sous-classes de la classe abstraite AquariumFish
.
Étape 2 : Créer une interface
- Dans AquariumFish.kt, créez une interface appelée
FishAction
avec une méthodeeat()
.
interface FishAction {
fun eat()
}
- Ajoutez
FishAction
à chacune des sous-classes et implémentezeat()
en lui demandant d'afficher ce que fait le poisson.
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()
, faites manger chaque poisson que vous avez créé 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 les classes Shark
et Plecostomus
, qui sont toutes deux composées de l'interface FishAction
et l'implémentent.
Quand utiliser des classes abstraites plutôt que des interfaces ?
Les exemples ci-dessus sont simples, mais lorsque vous avez de nombreuses classes interdépendantes, les classes abstraites et les 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, contrairement aux interfaces. À part cela, elles sont très similaires. Alors, quand utiliser chacun d'eux ?
Lorsque vous utilisez des interfaces pour composer une classe, la fonctionnalité de la classe est étendue par le biais des instances de classe qu'elle contient. La composition a tendance à rendre le code plus facile à réutiliser et à comprendre que l'héritage à partir d'une classe abstraite. De plus, vous pouvez utiliser plusieurs interfaces dans une classe, mais vous ne pouvez créer de sous-classe qu'à partir d'une seule classe abstraite.
La composition permet souvent une meilleure encapsulation, un couplage (interdépendance) plus faible, des interfaces plus claires et un code plus utilisable. Pour ces raisons, la composition avec des interfaces est la conception privilégiée. En revanche, l'héritage d'une classe abstraite a tendance à s'adapter naturellement à certains problèmes. Vous devez donc privilégier la composition, mais Kotlin vous permet également d'utiliser l'héritage lorsque cela est judicieux.
- Utilisez une interface si vous avez de nombreuses méthodes et 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, en revenant à la classe
AquariumFish
, vous pouvez faire en sorte que tous lesAquariumFish
implémententFishAction
et fournir une implémentation par défaut poureat
tout en laissantcolor
abstrait, car il n'y a pas vraiment de couleur par défaut pour les poissons.
interface FishAction {
fun eat()
}
abstract class AquariumFish: FishAction {
abstract val color: String
override fun eat() = println("yum")
}
La tâche précédente a présenté les classes abstraites, les interfaces et l'idée de composition. La délégation d'interface est une technique avancée dans laquelle les méthodes d'une interface sont implémentées par un objet d'assistance (ou délégué), qui est ensuite utilisé par une classe. Cette technique peut être utile lorsque vous utilisez une interface dans une série de classes non liées : vous ajoutez la fonctionnalité d'interface nécessaire à une classe d'assistance distincte, et chacune des classes utilise une instance de la classe d'assistance pour implémenter la fonctionnalité.
Dans cette tâche, vous allez utiliser la délégation d'interface pour ajouter des fonctionnalités à une classe.
Étape 1 : Créez 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 du poisson et sa 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 remplacer lecolor
deFishColor
et leeat()
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 interfaces,FishAction
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 une classe singleton
Ensuite, vous implémentez la configuration de la partie 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 l'or.
Il n'est pas logique de créer plusieurs instances de GoldColor
, car elles feraient toutes exactement la même chose. Kotlin vous permet donc de déclarer une classe dont vous ne pouvez créer qu'une seule instance en utilisant le mot clé object
au lieu de class
. Kotlin créera cette instance, qui sera référencée par le nom de la classe. Tous les autres objets peuvent alors 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 en Kotlin.
- Dans AquariumFish.kt, créez un objet pour
GoldColor
. Remplacez la couleur.
object GoldColor : FishColor {
override val color = "gold"
}
Étape 3 : Ajoutez la délégation d'interface pour FishColor
Vous êtes maintenant prêt à utiliser la délégation d'interface.
- Dans AquariumFish.kt, supprimez le remplacement de
color
à partir dePlecostomus
. - Modifiez la classe
Plecostomus
pour obtenir sa couleur à partir deGoldColor
. Pour ce faire, ajoutezby GoldColor
à la déclaration de classe afin de créer la délégation. Cela signifie qu'au lieu d'implémenterFishColor
, vous devez utiliser l'implémentation fournie parGoldColor
. Ainsi, chaque fois quecolor
est consulté, il est délégué àGoldColor
.
class Plecostomus: FishAction, FishColor by GoldColor {
override fun eat() {
println("eat algae")
}
}
Dans l'état actuel de la classe, tous les Plecos sont dorés, mais ces poissons existent en réalité dans de nombreuses couleurs. Pour résoudre ce problème, ajoutez un paramètre de constructeur pour la couleur avec GoldColor
comme couleur par défaut pour Plecostomus
.
- Modifiez la classe
Plecostomus
pour qu'elle accepte unfishColor
transmis avec 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 la 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 toute cette délégation, il n'y a pas de code dans le corps de la classe
Plecostomus
. Supprimez donc{}
, car toutes les substitutions sont gérées 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 leur déléguant l'implémentation.
La délégation d'interface est puissante. Vous devez généralement réfléchir à la façon de l'utiliser chaque fois que vous pourriez utiliser une classe abstraite dans une autre langue. Il vous permet d'utiliser la composition pour insérer des comportements, au lieu de nécessiter de nombreuses sous-classes, chacune spécialisée d'une manière différente.
Une classe de données est semblable à un struct
dans d'autres langages (elle existe principalement pour contenir des données), mais un objet de classe de données reste un objet. Les objets de classe de données Kotlin présentent des avantages supplémentaires, tels que des utilitaires d'impression et de copie. Dans cette tâche, vous allez créer une classe de données simple et découvrir la compatibilité de Kotlin avec les classes de données.
Étape 1 : Créer une classe de données
- Ajoutez un 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
comme préfixe à la déclaration de classe. - Ajoutez une propriété
String
appeléerocks
pour fournir des données à la classe.
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()
, puis exécutez votre programme. Notez la sortie pertinente qui est créée, 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 deuxième qui comparedecoration3
avecdecoration2
. Utilisez la méthode equals() 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 accéder aux propriétés d'un objet de données et les attribuer à des variables, vous pouvez les attribuer une par une, comme ceci.
val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver
Vous pouvez plutôt 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, un raccourci utile. Le nombre de variables doit correspondre au nombre de propriétés. 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 dans 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 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 des classes à usage spécifique de Kotlin, y compris les suivantes :
- Classes Singleton
- Enums
- Classes scellées
Étape 1 : Rappel sur les classes singleton
Reprenons l'exemple précédent avec la classe GoldColor
.
object GoldColor : FishColor {
override val color = "gold"
}
Étant donné que chaque instance de GoldColor
fait la même chose, elle est déclarée en tant que object
au lieu de class
pour en faire un singleton. Il ne peut y avoir qu'une seule instance.
Étape 2 : Créez une énumération
Kotlin est également compatible avec les énumérations, qui vous permettent d'énumérer un élément et de le désigner par son nom, comme dans d'autres langages. Déclarez une énumération en ajoutant le mot clé enum
comme préfixe à la déclaration. Une déclaration d'énumération de base n'a besoin que d'une liste de noms, mais vous pouvez également définir un ou plusieurs champs associés à chaque nom.
- Dans Decoration.kt, essayez 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'une seule, et une seule de chaque valeur dans l'énumération. Par exemple, il ne peut y avoir qu'un seul Color.RED
, un seul Color.GREEN
et un seul 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éer une classe scellée
Une classe scellée est une classe qui peut être sous-classée, mais uniquement dans le fichier dans lequel elle est déclarée. Si vous essayez de créer une sous-classe dans un autre fichier, une erreur s'affiche.
Comme 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 les classes, ce qui lui permet d'effectuer des vérifications supplémentaires pour vous.
- Dans AquariumFish.kt, essayez un exemple de classe scellée, en gardant 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 donc 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 sujets. Bien que la plupart des éléments devraient vous être familiers si vous avez déjà utilisé d'autres langages de programmation orientés objet, Kotlin ajoute certaines fonctionnalités pour rendre le code concis et lisible.
Classes et constructeurs
- Définissez une classe en 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 la classe. Exemples :
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
- Si un constructeur principal a besoin de 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 consiste à utiliser une fonction de fabrique à la place.
Modificateurs de visibilité et sous-classes
- Toutes les classes et fonctions en Kotlin sont
public
par défaut, mais vous pouvez utiliser des modificateurs pour modifier la visibilité eninternal
,private
ouprotected
. - Pour créer une sous-classe, la classe parente doit être marquée
open
. - Pour remplacer des méthodes et des propriétés dans une sous-classe, elles doivent être marquées
open
dans la classe parente. - Une classe scellée ne peut être sous-classée que dans le fichier où elle est définie. Créez une classe scellée en ajoutant le préfixe
sealed
à la déclaration.
Classes de données, singletons et énumérations
- Créez une classe de données en ajoutant le préfixe
data
à la déclaration. - 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 sont deux façons de partager un comportement commun entre les classes.
- Une classe abstraite définit des propriétés et un comportement, mais laisse l'implémentation aux sous-classes.
- Une interface définit un 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 par le biais des instances de classe 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. En général, la composition est préférable, mais l'héritage d'une classe abstraite est plus adapté à certains problèmes.
Documentation Kotlin
Si vous souhaitez en savoir plus sur un sujet abordé dans ce cours ou si vous êtes bloqué, https://kotlinlang.org est le meilleur point de départ.
- Classes 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
- Classes scellées
- Gérer les erreurs facultatives à l'aide des classes scellées Kotlin
Tutoriels Kotlin
Le site Web https://try.kotlinlang.org inclut des tutoriels complets appelés Kotlin Koans, un interpréteur Web et un ensemble complet de documentation de référence avec des exemples.
Cours Udacity
Pour consulter le cours Udacity sur ce sujet, reportez-vous à la formation Kotlin pour les programmeurs.
IntelliJ IDEA
La documentation d'IntelliJ IDEA est disponible sur le site Web de JetBrains.
Cette section répertorie les devoirs possibles pour les élèves qui suivent cet atelier de programmation dans le cadre d'un cours animé par un enseignant. Il revient à l'enseignant d'effectuer les opérations suivantes :
- Attribuer des devoirs si nécessaire
- Indiquer aux élèves comment rendre leurs devoirs
- Noter les devoirs
Les enseignants peuvent utiliser ces suggestions autant qu'ils le souhaitent, et ne doivent pas hésiter à attribuer d'autres devoirs aux élèves s'ils le jugent nécessaire.
Si vous suivez cet atelier de programmation par vous-même, n'hésitez pas à utiliser ces devoirs pour tester vos connaissances.
Répondre aux questions suivantes
Question 1
Les classes disposent d'une méthode spéciale qui sert de plan pour créer des objets de cette classe. Comment s'appelle cette méthode ?
▢ Un générateur
▢ Un instantiateur
▢ Un constructeur
▢ Un plan
Question 2
Parmi les affirmations suivantes concernant les interfaces et les classes abstraites, laquelle est FAUSSE ?
▢ Les classes abstraites peuvent avoir des constructeurs.
▢ Les interfaces ne peuvent pas avoir de constructeurs.
▢ Les interfaces et les classes abstraites peuvent être instanciées directement.
▢ Les propriétés abstraites doivent être implémentées par les 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
Considérez cette classe de données :data class Fish(val name: String, val species:String, val colors:String)
Parmi les propositions suivantes, laquelle 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 possédiez un zoo avec de nombreux animaux qui ont tous besoin d'être soignés. Parmi les éléments suivants, lequel ne fait PAS partie de l'implémentation de la protection ?
▢ Un interface
pour les différents types d'aliments que mangent les animaux.
▢ Une classe abstract Caretaker
à partir de laquelle vous pouvez créer différents types de responsables.
▢ Un interface
pour avoir donné de l'eau propre à un animal.
▢ Une classe data
pour une entrée dans un programme d'alimentation.
Passez à la leçon suivante :
Pour obtenir un aperçu du cours, y compris des liens vers d'autres ateliers de programmation, consultez Formation Kotlin pour les programmeurs : bienvenue dans le cours.