Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

L’héritage

Introduction

Maintenant que nous savons écrire des classes et instancier des objets, il y a d’autres concepts à explorer. Tous ces concepts nous permettront d’aller plus loin dans la programmation orientée objet et de rendre nos programmes informatiques plus maintenables et mieux architecturés.

Le premier concept que nous allons voir est celui de l’héritage.

Qu’est-ce que l’héritage ?

L’héritage est une technique qui permet de créer une classe à partir d’une autre classe afin de mutualiser du code. Mutualiser du code, c’est éviter d’écrire plusieurs fois du code qui pourrait être commun à plusieurs classes.

Reprenons la classe Dog. Nous avons donc un chien avec plusieurs attributs, à savoir un nom, un âge, une race, une couleur, une taille, un poids et une position. Cette classe possède plusieurs méthodes permettant de savoir si le chien est actuellement assis, ou bien couché, ou bien debout, de le nourrir, ou encore permettant au chien d’aboyer ou de courir.

Nous souhaitons maintenant ajouter une nouvelle classe Cat afin de représenter un chat. Ce chat a plusieurs attributs, comme un nom, un âge, une race, une couleur, une taille et un poids.

Cette nouvelle classe possède plusieurs méthodes permettant au chat de miauler, de courir ou permettant de le nourrir.

On remarque ici plusieurs similitudes entre le chien et le chat, que ce soit au niveau des attributs ou au niveau des méthodes. Ces similitudes sont tout à fait normales puisque, au fond, un chien et un chat sont des… animaux !

Nous pouvons donc créer une nouvelle classe Animal qui contiendra les attributs communs au chat et au chien. Les classes Cat et Dog hériteront...

Mettre en place l’héritage

Commençons par écrire une classe Animal. Elle doit contenir les attributs et les méthodes communs au chien et au chat.

1. La classe Animal

class Animal(var age: Int, var name: String, var race: String, 
var color: String, var size: Int, var weight: Float) 
{ 
 
 fun eat(foodWeight: Int) 
 { 
   weight += (foodWeight / 1000f) 
 } 
 
 fun run(distance: Int) 
 { 
   weight -= (distance / 1000f) / 1000 
 } 
 
 override fun toString(): String 
 { 
   return "Animal(age=$age, name='$name', race='$race', 
color='$color', size=$size, weight=$weight)" 
 } 
 
} 

Il s’agit d’une classe des plus normales. Malheureusement, en l’état, il n’est pas possible d’hériter de cette classe et donc de bâtir les classes Dog et Cat sur cette classe Animal. En effet, pour permettre l’héritage de la classe Animal, nous devons ajouter le mot-clé open devant le mot class. Le mot-clé open signifie que la classe est ouverte à l’héritage.

Ainsi, la classe Animal devient :

open class Animal(var age: Int, var name: String, var race: 
String, var color: String, var size: Int, var weight:...

Un nouveau droit d’accès

Puisqu’elles héritent de la classe Animal, les classes dog et cat peuvent manipuler les attributs et les méthodes de cette classe, car tout est public. Le problème c’est que ce niveau de visibilité permet aussi une manipulation des attributs depuis l’extérieur. Par exemple, le poids :

val dog = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
val cat = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f) 
 
dog.weight = 15f 
cat.weight = 105f 

Pour répondre à cette problématique, nous pourrions définir l’attribut weight de la classe Animal avec le droit d’accès private.

open class Animal(var age: Int, var name: String, var race: String, 
var color: String, var size: Int, private var weight: Float) 
{ 
 
  //... 
 
} 

Si nous ajoutons maintenant à la classe Dog une nouvelle méthode permettant de savoir si l’animal est en surpoids, nous avons besoin d’accéder à l’attribut weight :

import kotlin.math.pow 
 
class Dog(age: Int, name: String, race: String, color: String, 
size: Int, weight: Float, var position: Char = 't') 
: Animal(age, name, race, color, size, weight) 
{ 
 
 //... 
 
 fun...

La covariance

Pour aborder le concept de la covariance, reprenons le programme écrit précédemment :

val dog = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
val cat = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f) 

Que ce soit pour la variable dog ou la variable cat, nous ne précisons pas le type de la variable. Nous avons vu que ceci est possible grâce à l’inférence de type. En réalité, nous pouvons écrire :

val dog: Dog = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
val cat: Cat = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f) 

La covariance consiste, dans le cas d’une variable qui a pour valeur un objet, à déclarer le type de la variable avec le type de la classe mère malgré une instanciation avec une classe fille.

Dans ce cas, cela consiste donc à déclarer la variable dog ou la variable cat avec le type Animal :

val dog: Animal = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
val cat: Animal = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f) 

Dès lors que les variables dog et cat sont déclarées en tant qu’Animal, elles sont considérées comme tel. Cela signifie...

Vérifier le type d’un objet

Au sein de la fonction eatAnimal, nous souhaitons à présent savoir si l’animal que nous sommes en train de manipuler est un chien ou un chat. C’est tout à fait possible grâce aux conditions et au mot-clé is (est). Ce mot-clé doit être suivi du nom de la classe à tester.

Ainsi, nous pouvons créer les instructions suivantes : "si l’animal est un chien" ou "si l’animal est un chat".

En guise d’exemple, écrivons une fonction guessAnimal qui accepte un paramètre de type Animal et qui affiche le mot dog dans le terminal si le paramètre est de type Dog ou le mot cat si c’est un chat :

fun main() 
{ 
 val dog = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
 val cat = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f) 
 
 guessAnimal(dog) 
 guessAnimal(cat) 
} 
 
fun guessAnimal(animal: Animal) 
{ 
 if (animal is Dog) 
 { 
   println("dog") 
 } 
 else if (animal is Cat) 
 { 
   println("cat") 
 } 
} 

Il est également possible d’utiliser la structure when pour obtenir...

Le polymorphisme

Le polymorphisme est un concept essentiel, assez large. En quelque sorte, cela consiste, dans une classe fille, à redéfinir l’implémentation d’une méthode existant dans une classe mère.

Prenons un exemple. Dans la classe Animal, la méthode eat permet de nourrir un animal afin de le faire grossir et la méthode run permet de le faire courir afin de le faire maigrir. Ces deux méthodes utilisent des algorithmes pour faire changer le poids de l’animal. Cependant, tous les animaux ne réagissent probablement pas de la même façon à la nourriture ou à l’effort. Aussi, il peut être intéressant de proposer une implémentation par défaut dans la classe Animal et des implémentations spécifiques dans les classes Dog et Cat.

Contrairement à d’autres langages de programmation, comme Java, Kotlin est un langage qui, par défaut, a tendance à tout verrouiller. Pour rappel, pour autoriser l’héritage d’une classe, il est indispensable d’ajouter le mot-clé open. Pour permettre la redéfinition d’une méthode, il convient d’utiliser le même mot-clé sur les méthodes.

Revenons à la classe Animal :

open class Animal(var age: Int, var name: String, var race: String, 
var color: String, var size: Int...

Les classes abstraites

1. Qu’est-ce qu’une classe abstraite ?

Une classe abstraite est une classe que l’on ne peut pas instancier, c’est-à-dire une classe à partir de laquelle on ne peut pas créer d’objets.

À quoi ça sert ?

Créer une classe abstraite, donc une classe pour laquelle on ne peut pas créer d’instance, permet d’ajouter de la logique métier à une application informatique. Reprenons les classes Animal, Cat et Dog. Nous pouvons instancier des objets pour chacune de ces classes :

val dog = Dog(4, "Doggo", "labrador", "brown", 180, 12.5f, 's') 
 
val cat = Cat(2, "Kitten", "persan", "grey", 60, 6.3f) 
 
val animal = Animal(16, "Animal", "unknown", "green", 180, 12.5f) 

S’il est logique de pouvoir créer un chien ou un chat, est-ce logique de pouvoir créer un animal non identifié ?

La réponse est non ! La classe Animal doit donc logiquement être une classe abstraite.

2. Écrire une classe abstraite

Créer une classe abstraite est très simple. Il suffit d’utiliser le mot-clé abstract au moment de la déclaration de la classe, comme ceci :

abstract class Animal(var age: Int, var name: String, var race: 
String, var color: String, var size: Int, var weight: Float) 
{ 
 
 //... ...

En résumé

  • Pour permettre l’héritage d’une classe, il convient d’utiliser le mot-clé open.

  • Une classe hérite d’une autre classe grâce au symbole :.

  • Une classe ne peut hériter que d’une seule classe.

  • Une classe qui hérite d’une autre classe est appelée classe fille alors que la classe dont on hérite est appelée classe mère.

  • Quand on exprime un héritage, on doit toujours appeler l’un des constructeurs de la classe mère.

  • Le droit d’accès protected permet de rendre visible un attribut ou une méthode d’une classe mère à ses classes filles.

  • La covariance permet de manipuler un objet dans une variable dont le type est la classe mère.

  • Il est possible de vérifier le type d’un objet grâce au mot-clé is.

  • Le polymorphisme permet de redéfinir une méthode d’une classe mère dans une classe fille grâce aux mots -clés open et override.

  • Il est toujours possible de profiter de l’implémentation d’une méthode de la classe mère dans la classe fille grâce au mot-clé override.

  • Une classe abstraite est une classe que l’on ne peut pas instancier.

  • Une classe abstraite est définie grâce au mot-clé abstract.

  • Une classe abstraite peut contenir zéro, une ou plusieurs méthodes...