ProgrammationDéveloppeur Kotlin Middle/Senior

Comment fonctionne la délégation de comportement à travers des interfaces en Kotlin (délégation par interface) ? Quand doit-on l'utiliser, en quoi cela diffère-t-il de la délégation de propriétés et de l'héritage classique ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Kotlin, la délégation de comportement est implémentée par le mécanisme langage du mot clé by, directement dans la signature de la classe. Cela permet de transmettre automatiquement les appels de méthodes d'une interface (ou de plusieurs interfaces) à un autre objet avec une implémentation, réduisant le boilerplate et facilitant la composition.

Historique de la question

L'apparition de la délégation d'interface est une tentative d'éliminer les limitations et les inconvénients de l'héritage multiple. C'est l'idée de "composition plutôt qu'héritage" – nous déléguons le comportement sans avoir recours à une hiérarchie de classes. Inspiré de langages où la composition est plus populaire (par exemple, Go, Scala).

Problème

En Java et dans d'autres langages, il est souvent nécessaire de créer une interface et de mettre en œuvre manuellement chaque méthode, en transmettant la logique à un autre champ (patron Adaptateur d'Objet), ce qui devient rapidement obsolète avec l'augmentation du nombre de méthodes.

Solution

Kotlin permet de déléguer une interface de manière déclarative avec by :

interface Logger { fun log(msg: String) } class ConsoleLogger: Logger { override fun log(msg: String) = println(msg) } class Service(logger: Logger): Logger by logger { fun doWork() { log("Travail commencé") // ... } } val service = Service(ConsoleLogger()) service.doWork()
  • Toutes les méthodes Logger sont implémentées par l'objet logger fourni, sans que la classe Service n'ait besoin de redéfinir ou de proxy les méthodes explicitement.

Caractéristiques clés :

  • Permet de séparer l'implémentation de l'interface de son utilisation, réduisant la duplication de code
  • La délégation est plus flexible que l'héritage et fonctionne avec plusieurs comportements
  • Supporte les meilleures pratiques SOLID

Questions pièges.

Que se passe-t-il si nous ajoutons notre propre méthode d'interface avec la même signature dans la classe Service ?

La mise en œuvre propre "écrase" la délégation — donc la méthode définie explicitement dans la classe prévaut :

class Service(logger: Logger): Logger by logger { override fun log(msg: String) = println("PRÉFIXE: $msg") }

Un même classe peut-elle déléguer plusieurs interfaces à différents objets ?

Oui, une classe peut implémenter et déléguer plusieurs interfaces à différents objets, mais chaque interface est déléguée à un seul objet :

class Service( logger: Logger, tracker: Tracker ): Logger by logger, Tracker by tracker

En quoi la délégation d'interface diffère-t-elle de la délégation de propriétés via by ?

  • La délégation d'interface transmet toute l'implémentation des fonctions de l'interface à un autre objet.
  • La délégation de propriété (property delegation) délègue le travail de get/set à un objet délégué d'un type spécifique (ReadOnlyProperty, ReadWriteProperty).

Erreurs typiques et anti-patterns

  • Délégation d'interfaces trop larges (violation de l'ISP)
  • Application simultanée d'implémentation explicite et de délégation (comportement inattendu)
  • Tentative de combiner la délégation d'interface et l'héritage avec la classe parente, en ignorant l'ordre de résolution des méthodes

Exemple de la vie réelle

Cas négatif

La classe implémente manuellement l'interface, chaque méthode appelle le délégué, et en ajoutant de nouvelles méthodes, on oublie de mettre à jour le proxy, ce qui entraîne des erreurs.

Avantages :

  • La logique est clairement contrôlée

Inconvénients :

  • Risque élevé d'erreurs, boilerplate
  • Mauvaise évolutivité avec la croissance de l'interface

Cas positif

La délégation linguistique est utilisée, seules les méthodes non standard sont implémentées à l'intérieur de la classe, la nouvelle fonctionnalité est ajoutée sans changements majeurs.

Avantages :

  • Minimum de code
  • Contrôle clair des points d'extension

Inconvénients :

  • Nécessite de l'attention lors de l'implémentation combinée (il est facile d'obscurcir la méthode déléguée avec sa propre mise en œuvre)