ProgrammationDéveloppeur Android

Décrivez les différences entre les classes abstraites et les interfaces en Kotlin. Quand utiliser quelle approche, quelles sont les particularités d'implémentation dans le langage et quels sont les nuances de l'héritage ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Kotlin, les classes abstraites (abstract class) et les interfaces (interface) sont utilisées pour déclarer de la logique abstraite, mais il existe des différences essentielles entre elles :

  • Une classe abstraite peut contenir des états (champs), des implémentations de méthodes et des méthodes abstraites. Une classe ne peut hériter que d'une seule classe abstraite (ou normale). Les constructeurs peuvent être présents dans une classe abstraite.
  • Une interface peut contenir des déclarations de propriétés (sans stockage d'état !), des implémentations par défaut de méthodes et des méthodes abstraites. Depuis Kotlin 1.1, une interface peut contenir des implémentations de propriétés, mais elles ne peuvent pas avoir d'état. Une classe peut implémenter plusieurs interfaces.
  • Les interfaces ne peuvent pas contenir d'état (backing field), tandis que les classes abstraites peuvent.

Exemple de code

abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("Je suis $name") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("Miau") override fun move() = println("Le chat bouge") }

Question piégeuse.

Une interface en Kotlin peut-elle contenir des états (backing field) ?

La plupart des gens répondent qu'il est possible de déclarer des propriétés dans une interface, mais elles n'ont toujours pas de backing field — seulement des getters/setters sans stockage de valeur. Même si on écrit get() = ... — la valeur sera calculée à chaque fois.

Exemple :

interface MyInterface { val value: Int get() = 42 // pas de stockage de valeur, calcul à la volée }

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un grand projet, un développeur senior a essayé de stocker un état dans une interface (par exemple, un compteur), s'attendant à ce que la propriété var count: Int ait un backing field. En conséquence, toutes les classes implémentant l'interface devaient implémenter le stockage de valeur de manière différente, ce qui a abouti à une logique instable (les données étaient perdues si on oubliait de redéfinir le setter).


Histoire

Un des développeurs a utilisé une classe abstraite là où une implémentation multiple de différentes stratégies de comportement était requise (patron stratégie). Cela a entraîné un problème : une seule classe de base pour la hiérarchie, mais une composition était nécessaire pour différents aspects du comportement. Il a fallu changer l'architecture pour utiliser des interfaces.


Histoire

L'équipe a tenté d'hériter de deux classes abstraites en même temps (une avec la logique, l'autre avec des utilitaires communs), ce qui est impossible en Kotlin. Le problème a été identifié au stade d'extension de l'application. Cela a entraîné une dette technique importante, car les classes ont dû être réorganisées en urgence.