Guide des interfaces Kotlin

Guide des interfaces Kotlin

1. Vue d'ensemble

Dans ce didacticiel,we’ll discuss how to define and implement interfaces in Kotlin.

Nous allons également examiner comment plusieurs interfaces peuvent être implémentées par une classe. Cela peut certainement provoquer des conflits, et nous découvrirons le mécanisme dont dispose Kotlin pour les résoudre.

2. Interfaces à Kotlin

Une interface est un moyen de fournir une description ou un contrat pour des classes en programmation orientée objet. Ils peuvent contenir des propriétés et des fonctions de manière abstraite ou concrète en fonction du langage de programmation. Nous allons passer en revue les détails des interfaces dans Kotlin.

Les interfaces en Kotlin ressemblent aux interfaces de nombreux autres langages tels que Java. Mais ils ont une syntaxe spécifique, examinons-les dans les prochaines sous-sections.

2.1. Définition des interfaces

Commençons par définir notre première interface dans Kotlin:

interface SimpleInterface

C'est l'interface la plus simple qui est complètement vide. These are also known as marker interfaces.

Ajoutons maintenant quelques fonctions à notre interface:

interface SimpleInterface {
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, World!")
    }
}

Nous avons ajouté deux méthodes à notre interface définie précédemment:

  • L'une d'elles appelée firstMethod  est une méthode abstraite

  • Alors que l'autre appelé secondMethod has une implémentation par défaut.

Allons-y et ajoutons maintenant quelques propriétés à notre interface:

interface SimpleInterface {
    val firstProp: String
    val secondProp: String
        get() = "Second Property"
    fun firstMethod(): String
    fun secondMethod(): String {
        return("Hello, from: " + secondProp)
    }
}

Ici, nous avons ajouté deux propriétés à notre interface:

  • L'un d'eux appeléfirstProp is de type String et est abstrait

  • Le second appelésecondProp est également de type string mais il définit une implémentation pour son accesseur.

Notez queproperties in an interface cannot maintain state. Voici donc une expression illégale dans Kotlin:

interface SimpleInterface {
    val firstProp: String = "First Property" // Illegal declaration
}

2.2. Implémentation d'interfaces

Maintenant que nous avons défini une interface de base, voyons comment nous pouvons l'implémenter dans une classe de Kotlin:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
}

Notez que lorsque nous définissonsSimpleClass comme une implémentation deSimpleInterface,we only have to provide the implementation for abstract properties and functions. Cependant, nouscan override any previously defined property or function, too.

Remplaçons maintenant toutes les propriétés et fonctions précédemment définies dans notre classe:

class SimpleClass: SimpleInterface {
    override val firstProp: String = "First Property"
    override val secondProp: String
        get() = "Second Property, Overridden!"

    override fun firstMethod(): String {
        return("Hello, from: " + firstProp)
    }
    override fun secondMethod(): String {
        return("Hello, from: " + secondProp + firstProp)
    }
}

Ici, nous avons remplacé la propriétésecondProp et la fonctionsecondFunction qui étaient précédemment définies dans l'interfaceSimpleInterface.

2.3 Implementing Interfaces through Delegation

La délégation est un modèle de conception dans la programmation orientée objetto achieve code reusability through composition instead of inheritance. Bien que cela soit possible de l'implémenter dans de nombreux langages, comme Java,Kotlin has native support for implementation through delegation.

Si nous commençons par une interface de base et une classe:

interface MyInterface {
    fun someMethod(): String
}

class MyClass() : MyInterface {
    override fun someMethod(): String {
        return("Hello, World!")
    }
}

Jusqu'à présent, rien de nouveau. Mais maintenant, nous pouvons définir une autre classe qui implémenteMyInterface through délégation:

class MyDerivedClass(myInterface: MyInterface) : MyInterface by myInterface

MyDerivedClass attend un délégué comme argument qui implémente en fait l'interfaceMyInterface.

Voyons comment nous pouvons appeler une fonction de l'interface via un délégué:

val myClass = MyClass()
MyDerivedClass(myClass).someMethod()

Ici, nous avons instanciéMyClass et l'avons utilisé comme délégué pour appeler des fonctions de l'interface surMyDerivedClass, qui n'ont en fait jamais implémenté ces fonctions directement.

3. Héritage multiple

L'héritage multiple est un concept clé dans le paradigme de la programmation orientée objet. This allows for a class to inherit characteristics from more than one parent object, like an interface, for example.

Bien que cela offre plus de flexibilité dans la modélisation des objets, il comporte son propre ensemble de complexités. Le «problème des diamants» en est un.

Java 8 has its own mechanisms for addressing the diamond problem, comme tout autre langage autorisant l'héritage multiple.

Voyons comment Kotlin y répond via des interfaces.

3.1. Hériter de plusieurs interfaces

Nous allons commencer par définir deux interfaces simples:

interface FirstInterface {
    fun someMethod(): String
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in FirstInterface")
    }
}

interface SecondInterface {
    fun someMethod(): String {
        return("Hello, from someMethod in SecondInterface")
    }
    fun anotherMethod(): String {
        return("Hello, from anotherMethod in SecondInterface")
    }
}

Notez que les deux interfaces ont des méthodes avec le même contrat.

Définissons maintenant une classe qui hérite de ces deux interfaces:

class SomeClass: FirstInterface, SecondInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SomeClass")
    }
    override fun anotherMethod(): String {
        return("Hello, from anotherMethod in SomeClass")
    }
}

Comme nous pouvons le voir,SomeClass implémente à la foisFirstInterface etSecondInterface. Bien que syntaxiquement, ceci soit assez simple, il y a un peu de sémantique qui nécessite une attention particulière ici. Nous en reparlerons dans la prochaine sous-section.

3.2. Résoudre les conflits

Lors de l'implémentation de plusieurs interfaces, une classe peut hériter d'une fonction qui possède une implémentation par défaut pour le même contrat dans plusieurs interfaces. Cela pose le problème de l'invocation de cette fonction à partir d'une instance de la classe d'implémentation.

To resolve this conflict, Kotlin requires the subclass to provide an overridden implementation for such functions to make the resolution explicit.

Par exemple,SomeClass ci-dessus implémenteanotherMethod. Mais si ce n’était pas le cas, Kotlin ne saurait s’il faut invoquer l’implémentation par défaut deFirst  ouSecondInterface’s deanotherMethod. SomeClassmust implement anotherMethod for this reason.

Cependant,someMethod  est un peu différent puisqu'il n'y a en fait aucun conflit. FirstInterface  ne fournit pas d'implémentation par défaut poursomeMethod. Cela dit,SomeClass doit toujours l'implémenter carKotlin forces us to implement all inherited functions, no matter if they are defined once or multiple times in parent interfaces.

3.3. Résoudre le problème du diamant

Un «problème de diamant» se produit lorsque deux objets enfants d'un objet de base décrivent un comportement particulier défini par l'objet de base. Désormais, un objet héritant de ces deux objets enfants doit résoudre le comportement hérité auquel il est abonné.

La solution de Kotlin à ce problème passe par les règles définies pour l'héritage multiple dans la sous-section précédente. Définissons quelques interfaces et une classe d'implémentation pour présenter le problème du diamant:

interface BaseInterface {
    fun someMethod(): String
}

interface FirstChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in FirstChildInterface")
    }
}

interface SecondChildInterface: BaseInterface {
    override fun someMethod(): String {
        return("Hello, from someMethod in SecondChildInterface")
    }
}

class ChildClass: FirstChildInterface, SecondChildInterface {
    override fun someMethod(): String {
        return super.someMethod()
    }
}

Ici, nous avons définiBaseInterface qui a déclaré une fonction abstraite appeléesomeMethod. Les interfacesFirstChildInterface etSecondChildInterface héritent deBaseInterface et implémentent la fonctionsomeMethod.

Maintenant que nous implémentonsChildClass héritant deFirstChildInterface etSecondChildInterface, il nous est nécessaire de remplacer la fonctionsomeMethod. Cependant,even though we must override the method, we can still simply call super as we do here with SecondChildInterface.

4. Interfaces comparées aux classes abstraites dans Kotlin

Les classes abstraites dans Kotlin sontclasses which cannot be instantiated. Cela peut contenir une ou plusieurs propriétés et fonctions. Ces propriétés et fonctions peuvent être abstraites ou concrètes. Toute classe héritant d'une classe abstraite doit implémenter toutes les propriétés et fonctions abstraites héritées, à moins que cette classe elle-même ne soit également déclarée abstraite.

4.1. Différences entre l'interface et la classe abstraite

Attendez! Cela ne ressemble-t-il pas exactement à ce que fait une interface?

En fait, au départ, une classe abstraite n'est pas très différente de l'interface. Mais, il y a des différences subtiles qui régissent le choix que nous faisons:

  • Une classe dans Kotlin peut implémenter autant d’interfaces qu’elle le souhaite, mais elle ne peut s’étendre que d’une classe abstraite.

  • Les propriétés dans l'interface ne peuvent pas maintenir l'état, alors qu'elles peuvent dans une classe abstraite

4.2. Quand devons-nous utiliser quoi?

Une interface est juste un plan pour la définition des classes, elles peuvent éventuellement aussi avoir des implémentations par défaut. D'autre part, une classe abstraite est une implémentation incomplète complétée par les classes d'extension.

Généralementinterfaces should be used to define the contract, ce qui permet d'obtenir les capacités qu'il promet de fournir. Une classe d'implémentation a la responsabilité de tenir ces promesses. Unabstract class, however, should be used to share partial characteristics with extending classes. Une classe en extension peut aller plus loin pour la compléter.

5. Comparaison avec les interfaces Java

Avec les modifications apportées à l'interface Java dans Java 8,they have come very close to Kotlin interfaces. Un de nos articles précédents capturethe new features introduced in Java 8 including changes to the interface.

Il existe actuellement des différences syntaxiques entre les interfaces Java et Kotlin. Une différence qui se démarque est liée au mot clé «override». Dans Kotlin, lors de l'implémentation de propriétés abstraites ou de fonctions héritées d'une interface, il est obligatoire de les qualifier avec le mot-clé «override». Il n’existe pas de telle exigence explicite en Java.

6. Conclusion

Dans ce tutoriel, nous avons abordé les interfaces Kotlin, comment les définir et les implémenter. Nous avons ensuite parlé de l’héritage de plusieurs interfaces et du conflit qu’elles peuvent créer. Nous avons examiné comment Kotlin gérait de tels conflits.

Enfin, nous avons discuté des interfaces par rapport aux classes abstraites de Kotlin. Nous avons également brièvement expliqué comment l'interface Kotlin se compare à l'interface Java.

Comme toujours, le code des exemples est disponibleover on GitHub.