Les interfaces
Qu’est-ce qu’une interface ?
En Kotlin, une interface est ce qu’on appelle un contrat. Elle permet d’imposer un comportement à respecter à travers un ensemble de méthodes définies uniquement par leurs signatures. Ces méthodes devront être implémentées par la classe qui implémentera l’interface.
À quoi ça sert ?
Quand on débute en programmation et plus particulièrement en programmation orientée objet, comprendre l’intérêt des interfaces n’est pas toujours évident et leur utilisation n’est pas toujours naturelle.
Illustrons l’intérêt des interfaces en reprenant l’exemple des animaux. Jusqu’à maintenant, nous avons manipulé trois classes, à savoir une classe abstraite représentant un animal, une classe représentant un chat et héritant de la classe représentant un animal et une classe représentant un chien et héritant également de la classe représentant un animal.
Les deux animaux représentés ici ont un point commun : il s’agit d’animaux domestiques.
Ajoutons à présent de nouveaux comportements à travers de nouvelles méthodes : lécher, se blottir. Pour des raisons pédagogiques, nous souhaitons que les animaux domestiques implémentent...
Écrire une interface
Écrivons l’interface permettant d’imposer un comportement à un animal domestique.
Pour créer cette première interface, rendez-vous dans IntelliJ IDEA.
Cliquez du bouton droit sur le dossier src du programme, puis sur New et finalement sur Kotlin File/Class.
Dans la boîte de dialogue qui s’affiche, saisissez le nom de l’interface, ici Domestic.
Puis sélectionnez Interface comme type de fichier.
Pour finaliser la création de ce nouveau fichier, appuyez sur la touche [Entrée] du clavier.
Un fichier Domestic.kt apparaît alors dans le dossier src du programme. Son contenu est le suivant :
interface Domestic
{
}
C’est le code minimal pour créer une interface. Pour déclarer une interface, il convient donc d’utiliser le mot-clé interface, suivi du nom de l’interface. Dans ce cas : Domestic.
Comme pour une classe, la convention de nommage en Kotlin stipule que le nom d’une interface débute toujours par une majuscule et respecte la notation CamelCase.
Pour le moment il n’y a pas de code entre les accolades de cette interface, mais c’est entre celles-ci que nous allons écrire les comportements spécifiques aux animaux domestiques, à savoir lécher et se blottir.
Une interface peut, dans un cadre pédagogique...
Implémenter une interface
Maintenant que l’interface est en place, il convient de la relier aux classes Cat et Dog qui représentent toutes les deux des animaux domestiques.
Pour traduire le fait que la classe Cat implémente l’interface Domestic, on écrit, avant l’accolade ouvrante de la classe, le symbole : suivi du nom de l’interface que l’on souhaite implémenter, dans ce cas l’interface Domestic :
class Cat(age: Int, name: String, race: String, color: String,
size: Int, weight: Float)
: Domestic
{
//...
}
La syntaxe est très proche de celle permettant d’exprimer l’héritage. À ce propos, dans cet exemple, la classe Cat n’hérite plus de la classe Animal. Corrigeons tout de suite ce problème.
En Kotlin, pour exprimer le fait qu’une classe hérite d’une autre classe et implémente une interface, on utilise le symbole : suivi du nom de la classe dont on hérite (ici Animal) et du constructeur de classe mère à appeler. Vient ensuite le symbole , suivi du nom de l’interface à implémenter (ici Domestic).
class Cat(age: Int, name: String, race: String, color: String,
size: Int, weight: Float)
: Animal(age, name, race, color, size, weight),
Domestic ...
Ajouter des attributs et des implémentations par défaut dans les interfaces
Précédemment, les interfaces ont été qualifiées de classes 100 % abstraites dans lesquelles on n’écrit que les signatures des méthodes du contrat. Les interfaces ne sont en réalité pas aussi simplistes que cela et peuvent également porter des attributs et des méthodes non abstraites.
1. Les méthodes non abstraites
Comme nous le faisons dans une classe abstraite, il est possible d’ajouter des méthodes non abstraites dans une interface. Les classes qui implémentent alors cette interface peuvent faire le choix de conserver le comportement par défaut ou de surcharger ce comportement en redéfinissant le corps de la méthode. Exactement comme nous l’avons fait dans le chapitre L’héritage à la section Le polymorphisme.
Afin d’illustrer ces propos, modifions légèrement l’interface Domestic. La méthode qui permet à un animal de lécher a dorénavant une implémentation par défaut :
interface Domestic
{
fun snuggle()
fun lick()
{
println("lick! Lick!")
}
}
C’est tout ! Libre à vous ensuite de surcharger la méthode...
La covariance
Comme pour les classes abstraites, il est toujours possible de manipuler une interface et de l’utiliser en mettant en œuvre la covariance.
Exemple
fun main()
{
val cat = Cat(2, "Kitty", "Persan", "grey", 37, 6.21f, "A14C")
snuggle(cat)
}
fun snuggle(animal: Domestic)
{
animal.snuggle()
}
Créer une classe anonyme
À travers une classe anonyme, il est possible d’instancier une classe abstraite (voir le chapitre L’héritage), mais aussi une interface, avec une syntaxe similaire.
Une classe anonyme permet donc de créer une instance d’une interface. On la crée au moment où l’on souhaite affecter la valeur de l’interface à une variable.
Exemple
val domestic = object : Domestic
{
override val tatoo: String
get() = "A12B"
override fun snuggle()
{
println("snuggle!")
}
}
En résumé
-
Une interface peut être considérée comme un contrat qui expose des méthodes abstraites, des attributs abstraits et des méthodes classiques.
-
Une interface est définie grâce au mot-clé interface.
-
Une interface n’est pas incompatible avec le concept de la covariance.
-
Il est possible de créer une instance d’une interface grâce à ce que l’on appelle les classes anonymes.