Temps : 45, 20, 30, 15 min.
Difficulté : ***
Cette partie aborde les fondamentaux de la POO avec le langage Kotlin, selon les points suivants :
En programmation, les packages servent à garder le code organisé.
agua
En Kotlin, une classe est défini avec le mot clé class
.
Note : Le nom d'une classe commence par une majuscule.
agua
Boat
Boat
, définissez et initialisez les variables length, width, floor
package agua
class Boat {
var length: Int = 10
var width: Int = 5
var floor: Int = 1
}
Note : Il n'y a pas besoin d'écrire les getters et setters,
ils sont implicites. De plus, par défaut tout est publique, d'où l'absence
du mot clé public
.
En programmation, la fonction main()
est le point d'entré d'un programme.
En Kotlin, une fonction est déclaré avec le mot clé fun
.
agua
Main.kt
buildBoat()
, elle créé une instance de Boat
package agua
fun buildBoat() {
val myBoat = Boat()
}
Note : Pour créer une instance, référencez la classe comme s'il s'agissait d'une fonction (Boat()
)
Cela appelle le constructeur de la classe et crée une instance de cette dernière,
similaire à l'utilisation du mot clé new dans d'autres langages.main()
, elle appelle buildBoat()
package agua
...
fun main() {
buildBoat()
}
main()
(Control + Shift + R)Une fonction membre, member function, ou méthode est une fonction pouvant s'appliquer sur un objet spécifique.
Boat
, ajoutez une méthode d'affichage :
fun printSize() {
println("Length: $length m " +
"Width: $width m " +
"Floor: $floor m ")
}
Main.kt
, depuis buildBoat()
,
appelez la méthode printSize()
sur myBoat
fun buildBoat() {
val myBoat = Boat()
myBoat.printSize()
}
buildBoat()
, modifiez un des attributs de myBoat
,
puis affichez le changement
fun buildBoat() {
val myBoat = Boat()
myBoat.printSize
// modification de la hauteur
myBoat.floor = 60
myBoat.printSize()
}
En Kotlin, il y a deux types de constructeurs :
constructor
Boat
,
sans constructeur, avec des propriétés par défaut. Boat
, changez l'entête de classe
pour inclure un constructeur primaire avec des valeurs par défaut,
modifiez les propriétés en conséquence :
class Boat(length: Int = 10, width: Int = 5, floor: Int = 1) {
// Dimensions en cm
var length: Int = length
var width: Int = width
var floor: Int = floor
...
}
class Boat(var length: Int = 10, var width: Int = 5, var floor: Int = 1) {
...
}
Main.kt
,
buildBoat()
, en créant différentes instances de bateau :
val Boat1 = Boat()
boat1.printSize()
// default floor and length
val Boat2 = Boat(width = 25)
boat2.printSize()
// default width
val boat3 = Boat(floor = 35, length = 110)
boat3.printSize()
// everything custom
val boat4 = Boat(width = 25, floor = 35, length = 110)
boat4.printSize()
main()
(Control + Shift + R),
observez le résultat attenduEn Kotlin, il existe le bloc d'initialisation, init
.
Il permet de placer du code d'initialisation lorsque le constructeur en a besoin.
Boat
, placez un bloc init
:
class Boat(var length: Int = 10, var width: Int = 5, var floor: Int = 1) {
init {
println("Boat in construction")
}
}
En Kotlin, il est possible de déclarer les deux types de constructeur
primaire et secondaire dans la même classe. Cela permet de faire de la surcharge
de constructeur, constructor overloading, avec des arguments différents.
Lorsqu'il y a plusieurs constructeur, il s'appelle les uns des autres avec
le mot clé this
(et des arguments null
,
cf. Challenge : Créer une vue personnalisée).
Note : L'ordre d'exécution des constructeurs et bloc d'initialisation est
Boat
, ajoutez un constructeur secondaire :
constructor(numberOfPeople: Int) : this() {
// nombre de personne pouvant être accueillie
val area = numberOfPeople * 2
}
// la largueur du bateau est fixe, la hauteur est flexible,
// il s'agit de calculer le nombre d'étage nécessaire pour le bien être des passagers
floor = area / (length * width)
Main.kt
, appellez le constructeur ainsi créé
depuis buildBoat()
:
fun buildBoat() {
val boat6 = Boat(numberOfFish = 29)
boat6.printSize()
println("Area: ${boat6.width * boat6.length * boat6.floor} m2")
}
En Kotlin, l'accesseur et le mutateur d'une propriété est automatiquement créés. Cependant, il est possible de créer un mutateur, ou un accesseur, personnalisé pour chaque propriété.
Boat
, définissez une propriété personnalisée :
// la superficie se calcule à partir des autres propriétés longueur et largueur
val area: Int
get() = length * width * floor
init
affichant la superficiebuildBoat()
printSize()
, ajoutez l'affichage de la superficie
fun printSize() {
println("Length: $length m " +
"Width: $width m " +
"Floor: $floor ")
println("Area: $area m2")
}
Boat
, changez la propriété area
immuable en mutable var
var area: Int
get() = length * width * floor
set(value) {
floor = (value) / (length * width)
}
buildBoat()
, ajoutez la modification de la superficie
fun buildBoat() {
val boat6 = Boat(numberOfPeople = 29)
boat6.printSize()
boat6.area = 70
boat6.printSize()
}
En Kotlin, les classes, les objets, les interface, les constructeurs,
les fonctions, les propriétés et leurs accesseurs peuvent avoir des modificateurs de visibilité.
Par défaut, tout est publique, public
.
public |
visible au dehors de la classe |
private |
seulement visible à l'intérieur de la classe |
protected |
visible également par les sous classes |
internal |
visible dans le module (c'est un ensemble de classe ou fichier compiler ensemble) |
En Kotlin, l'héritage n'est pas automatique, une classe mère doit être
déclaré open
, de façon a lui permettre d'être une super classe.
De même les propriétés et membre doivent être déclaré open
.
open
L'objectif est de transformer le bateau en une classe mère.
boat
, changez la signature de façon a déclaré la classe open
open
open var area: Int
get() = length * width * floor
set(value) {
floor = value / (length * width)
}
open val material = "wood"
open var areaForPeople: Double = 0.0
get() = area * 0.5
fun printSize() {
println("The boat is in $material (material)")
println("Length: $length m " +
"Width: $width m " +
"Floor: $floor ")
println("Area: $area m2; Area for People: $areaForPeople (${areaForPeople / area * 100.0} % full)")
}
buildBoat()
, créez une instance de bateau comme suit :
fun buildBoat() {
val boat6 = Boat(length = 25, width = 25, floor = 40)
boat6.printSize()
}
Il s'agit de créer une sous classe ou classe fille. Contexte : Le bateau, pouvant également représenter un navire, est trop général, nous souhaitons à présent créer un voilier.
Boat
,
à la suite de la classe Boat
, déclarez une classe SailBoat
class SailBoat(override var length: Int, override var width: Int) :
Boat(length = length, width = width, floor = 2)
override var area: Int
// area = width * length / 3
get() = length * width / 3 * floor
set(value) {
floor = value / length / width
}
override var areaForPeople = area * 0.25
override val material = "wood"
package agua
class SailBoat(override var length: Int, override var width: Int) :
Boat(length = length, width = width, floor = 2) {
override var area: Int
// area = width * length / 3
get() = length * width / 3 * floor
set(value) {
floor = value / length / width
}
override var areaForPeople = area * 0.25
override val material = "wood"
}
buildBoat()
, créez une instance de voilier :
fun buildBoat() {
val myBoat = Boat(width = 25, length = 25, floor = 40)
myBoat.printSize()
val sail = SailBoat(width = 25, length = 25)
sail.printSize()
}
Dans certain cas, nous souhaitons définir des comportements identiques pour
des propriétés ou des classes.
Par exemple, nous allons créer :
Une classe abstraite est partiellement définie. C'est de la responsabilité
de la sous classe de définir les méthodes et propriétés.
Par défaut une classe abstract
est open
, il n'y a pas
besoin de le spécifier.
Elle peut avoir des propriétés et des méthodes abstraite, dans ce cas la sous
classe est en charge de les définir.
Elle peut aussi définir un constructeur commun pour toute les sous classes.
agua
, créez un nouveau fichier Fish.kt
Fish
déclarez là comme abstraite :
package agua
abstract class Fish
abstract val color: String
Fish
: Shark
et Plecostomus
color
est abstraite, Shark
et Plecostomus
doivent la définir
class Shark: Fish() {
override val color = "grey"
}
class Plecostomus: Fish() {
override val color = "gold"
}
Main.kt
, créez une fonction makeFish()
afin d'y instancier des poissons
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
println("Plecostomus: ${pleco.color}")
}
makeFish()
dans le main
,
exécutez le programme et observez le résultat
Fish.kt
, créez une interface avec une méthode
interface FishAction {
fun eat()
}
FishAction
aux deux sous classeseat()
:
class Shark: Fish(), FishAction {
override val color = "grey"
override fun eat() {
println("hunt and eat fish")
}
}
class Plecostomus: Fish(), FishAction {
override val color = "gold"
override fun eat() {
println("eat algae")
}
}
makeFish()
, faites manger les poissons
fun makeFish() {
val shark = Shark()
val pleco = Plecostomus()
println("Shark: ${shark.color}")
shark.eat()
println("Plecostomus: ${pleco.color}")
pleco.eat()
}
L'astuce est d'utiliser une classe abstraite tant qu'il n'est pas possible de compléter la classe.
Fish.kt
, modifiez la classe abstraite
pour générer une implémentation générale pour le comportement du poisson
abstract class Fish : FishAction {
abstract val color: String
override fun eat() = println("yum")
}
class RedFish : Fish() {
override val color = "red"
}
makeFish()
testez le poisson rouge
fun makeFish() {
val fish = RedFish()
println("Fish: ${fish.color}")
fish.eat()
}
En Kotlin, les qualificatifs de classe les plus connus sont :
data
: représente une donnéenested
: une classe dans la classe (static class
in Java)inner
: visibilité des membres élargie (ajoute une référence à une classe extérieur)enum
: représente une énumérationsealed
: restreint la hiérarchie de la classe (le nombre de sous classe est fixe, elles sont
placées dans le même fichier)La classe de donnée, ou data class
, est une bonne pratique afin d'indiquer la simplicité
de l'objet.
Déclarée avec le mot clé data
, la classe a accès a des fonctions
générées automatiquement, tel que la fonction equals()
, hascode()
,
toString()
...
agua
, nommez-le deco
Decoration
:
data class Decoration(val rocks: String)
Decoration.kt
, à l'extérieur de la classe,
ajoutez une fonction makeDecorations()
pour afficher des instances de la classe de donnée
fun makeDecorations() {
val decoration1 = Decoration("granite")
println(decoration1)
}
main()
pour tester, exécutezmakeDecorations()
fun makeDecorations() {
val decoration1 = Decoration("granite")
val d2 = Decoration("crystal")
val d3 = Decoration("crystal")
println("$decoration1 $d2 $d3")
}
makeDecorations()
, testez l'égalité des instances :
println("deco 1 et deco 2 : ${decoration1.equals(d2)}")
println("deco 2 et deco 3 (equals): ${d2.equals(d3)}")
println("deco 2 et deco 3 (==): ${d2 == d3}")
println("deco 2 et deco 3 (===): ${d2 === d3}")
Note : ==
et equals
c'est pareil (operator overloading),
===
correspond à l'égalité sur la référence de l'objet
La classe d'énumération, ou enum class
, est une bonne pratique
afin d'énumerer des valeurs.
Cela dit, il est plus courant d'utiliser le singleton (companion object
).
Decoration.kt
, déclarez une classe d'énumération :
enum class Direction(val degrees: Int) {
NORTH(0), SOUTH(180), EAST(90), WEST(270)
}
main
testez :
println(Direction.EAST.name)
println(Direction.EAST.ordinal)
println(Direction.EAST.degrees)
Finalement, Kotlin est un langage de programmation fonctionnelle et orientée objet par rapport à :
class
, open
, abstract
, interface
, data
, enum
constructor
et le bloc init