Principes SOLID en Kotlin : théorie et pratique à un niveau avancé
\n\nSOLID est un ensemble fondamental de principes de conception orientée objet, introduit par Robert Martin. Dans le contexte de Kotlin, qui combine les meilleures caractéristiques de la POO et de la programmation fonctionnelle, ces principes acquièrent de nouvelles possibilités de mise en œuvre. Dans cet article, nous examinerons chaque principe à un niveau avancé, en utilisant les fonctionnalités uniques du langage : les fonctions inline, la délégation, les classes sealed et les fonctions d'extension.
\n\n1. Principe de Responsabilité Unique (SRP) et délégation
\n\nLe principe de responsabilité unique stipule qu'une classe ne doit avoir qu'une seule raison de changer. En Kotlin, pour respecter le SRP, on utilise souvent le modèle Délégation (Delegation). Le mot-clé by permet de déléguer automatiquement l'implémentation d'une interface à un autre objet.
Problème : La classe UserService est responsable à la fois de la logique métier, de la journalisation et de l'envoi d'e-mails.
// ❌ Violation du SRP\nclass UserService {\n fun registerUser(email: String, password: String) {\n // Logique métier\n println("Utilisateur enregistré")\n // Journalisation\n log("Utilisateur enregistré : $email")\n // Envoi d'e-mail\n sendEmail(email, "Bienvenue !")\n }\n\n private fun log(message: String) { /* ... */ }\n private fun sendEmail(to: String, body: String) { /* ... */ }\n}\n\n\nSolution utilisant la délégation :
\n\n// ✅ Respect du SRP via la délégation\ninterface Logger {\n fun log(message: String)\n}\n\ninterface EmailSender {\n fun send(to: String, body: String)\n}\n\nclass ConsoleLogger : Logger {\n override fun log(message: String) = println("LOG : $message")\n}\n\nclass SmtpEmailSender : EmailSender {\n override fun send(to: String, body: String) = println("Envoi d'e-mail à $to : $body")\n}\n\nclass UserService(\n private val logger: Logger,\n private val emailSender: EmailSender\n) : Logger by logger, EmailSender by emailSender { // 👈 Délégation\n\n fun registerUser(email: String, password: String) {\n println("Utilisateur enregistré")\n log("Utilisateur enregistré : $email")\n send(email, "Bienvenue !")\n }\n}\n\n// Utilisation\nfun main() {\n val service = UserService(ConsoleLogger(), SmtpEmailSender())\n service.registerUser("user@example.com", "pass123")\n}\n\n\nL'utilisation de by permet d'éviter le code passe-partout et de séparer clairement les responsabilités. La classe UserService est désormais uniquement responsable de l'enregistrement, tandis que la journalisation et l'envoi sont délégués à des classes distinctes.
2. Principe Ouvert-Fermé (OCP) et classes sealed
\n\nPrincipe d'ouverture/fermeture : les classes doivent être ouvertes à l'extension, mais fermées à la modification. En Kotlin, les classes sealed et les fonctions d'extension sont idéales pour cela.
\n\nProblème : La fonction processPayment utilise when avec une vérification de type, et lors de l'ajout d'un nouveau mode de paiement, elle doit être modifiée.
// ❌ Violation de l'OCP\nfun processPayment(type: String, amount: Double) {\n when (type) {\n "card" -> println("Traitement du paiement par carte : $amount")\n "paypal" -> println("Traitement du paiement PayPal : $amount")\n else -> throw IllegalArgumentException("Type de paiement inconnu")\n }\n}\n\n\nSolution avec les classes sealed :
\n\n// ✅ Respect de l'OCP\nsealed class PaymentMethod {\n data class Card(val number: String, val cvv: String) : PaymentMethod()\n data class PayPal(val email: String) : PaymentMethod()\n object Cash : PaymentMethod()\n}\n\n// Fonction fermée à la modification\nfun processPayment(method: PaymentMethod, amount: Double) {\n // Lorsque de nouvelles sous-classes apparaîtront, le compilateur exigera leur traitement\n