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é.
aguaEn Kotlin, une classe est défini avec le mot clé class.
  Note : Le nom d'une classe commence par une majuscule.
  
aguaBoatBoat, 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.
  
aguaMain.ktbuildBoat(), 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 :
constructorBoat,
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.
openL'objectif est de transformer le bateau en une classe mère.
boat, changez la signature de façon a déclaré la classe openopen
    
    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.ktFish déclarez là comme abstraite :
  
  package agua
  abstract class Fish
  
  
  
  
  abstract val color: String
  
  
  Fish : Shark et Plecostomuscolor 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 decoDecoration : 
    
    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, enumconstructor et le bloc init