Kotlin prend en charge les paramètres d'héritage via le mot clé open, mais la principale recommandation du langage est de privilégier la composition plutôt que l'héritage. Cela permet de créer des systèmes plus flexibles et extensibles, évitant la fragilité des hiérarchies et les problèmes liés à une héritage profond.
La composition consiste à inclure un objet du type requis en tant que champ de la classe et à lui déléguer le travail, plutôt que de hériter de sa mise en œuvre. Kotlin facilite le modèle de délégation grâce au mot clé by, permettant de déléguer automatiquement la mise en œuvre des interfaces à des objets.
Exemple de modèle de délégation :
interface Logger { fun log(message: String) } class ConsoleLogger : Logger { override fun log(message: String) = println(message) } class Service(private val logger: Logger) : Logger by logger { fun doAction() { log("Action done") } } fun main() { val service = Service(ConsoleLogger()) service.doAction() // Affichera : Action done }
Cette approche simplifie la réutilisation du code et rend la logique plus modulaire.
"Une data class peut-elle hériter d'une autre classe, par exemple d'une classe abstraite ?"
data class en Kotlin ne peut pas hériter d'une autre classe (à l'exception des interfaces), car data class est toujours final. L'exception est les interfaces, qui peuvent être implémentées.Exemple :
abstract class Base(val name: String) data class Derived(val age: Int, val name: String) : Base(name) // Erreur de compilation : data class ne peut pas étendre la classe Base
Mais c'est possible :
interface User data class Admin(val name: String, val rights: List<String>) : User
Histoire
Dans un projet, il a été décidé de faire hériter plusieurs services d'une classe abstraite commune pour implémenter une logique répétée. Finalement, cela a conduit à de nombreux niveaux d'héritage, compliquant le débogage et entraînant des problèmes de test. Après être passé à la composition et à la délégation (via des interfaces et l'injection de dépendances), il a été possible de simplifier le code, de le rendre plus modulaire et d'améliorer la couverture des tests.
Histoire
Un développeur débutant a essayé d'étendre une data class à l'aide d'une autre classe pour ajouter une fonctionnalité commune. Le code ne compilait pas, mais le programmeur n'a pas pu comprendre la raison pendant longtemps (limitations de data class en Kotlin).
Histoire
Dans un projet avec une logique de journalisation étendue, il a été décidé d'extraire la fonctionnalité de journalisation dans une classe de base. Cependant, avec la croissance du système, certaines services nécessitaient une autre implémentation de la journalisation. Il a fallu effectuer un refactoring à l'aide de l'interface Logger et de la composition via la délégation, ce qui a considérablement simplifié l'architecture.