Introduction : pourquoi SOLID est-il important en Kotlin ?
SOLID est l'acronyme de cinq principes fondamentaux de la programmation orientée objet, formulés par Robert Martin (Oncle Bob). Dans l'écosystème Kotlin, qui combine les meilleures caractéristiques de la programmation orientée objet et fonctionnelle, le respect des principes SOLID aide à créer un code flexible, testable et maintenable. Ces principes sont particulièrement pertinents pour le développement Android, les applications serveur avec Ktor et les systèmes multithreads basés sur les coroutines.
Dans cet article, nous analyserons chacun des cinq principes SOLID avec des exemples adaptés aux idiomes de Kotlin. Vous apprendrez à éviter les erreurs courantes et à rendre votre code non seulement fonctionnel, mais véritablement de qualité.
1. Single Responsibility Principle (SRP) — principe de responsabilité unique
Essence du principe
Une classe ne doit avoir qu'une seule raison de changer. En d'autres termes, chaque classe est responsable d'exactement une fonctionnalité.
Mauvais exemple (violation du SRP)
class UserManager { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ } fun sendEmail(to: String, subject: String) { /* ... */ } fun generateReport(userId: Int): String { /* ... */ }}Ici, UserManager est responsable à la fois de la gestion des utilisateurs, de l'envoi d'e-mails et de la génération de rapports. Un changement dans la logique des e-mails entraînerait une modification de cette classe, alors qu'elle ne devrait pas concerner la messagerie.
Bon exemple (respect du SRP)
class UserRepository { fun createUser(name: String, email: String) { /* ... */ } fun deleteUser(id: Int) { /* ... */ }}
class EmailService { fun sendEmail(to: String, subject: String) { /* ... */ }}
class ReportGenerator { fun generateReport(userId: Int): String { /* ... */ }}Maintenant, chaque classe a une seule raison de changer. Cela simplifie les tests et la maintenance du code.
2. Open-Closed Principle (OCP) — principe d'ouverture/fermeture
Essence du principe
Les entités logicielles (classes, modules, fonctions) doivent être ouvertes à l'extension, mais fermées à la modification. Cela signifie qu'un nouveau comportement est ajouté par héritage ou composition, et non en modifiant le code existant.
Mauvais exemple (violation de l'OCP)
class PaymentProcessor { fun process(paymentType: String) { when (paymentType) { "credit_card" -> // traitement de la carte de crédit "paypal" -> // traitement PayPal else -> throw IllegalArgumentException() } }}Lors de l'ajout d'un nouveau mode de paiement (par exemple, Apple Pay), il faudrait modifier PaymentProcessor, ce qui viole l'OCP.
Bon exemple (respect de l'OCP)
interface PaymentMethod { fun process(amount: Double)}
class CreditCardPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PayPalPayment : PaymentMethod { override fun process(amount: Double) { /* ... */ }}
class PaymentProcessor(private val method: PaymentMethod) { fun processPayment(amount: Double) { method.process(amount) }}Désormais, pour ajouter un nouveau mode de paiement, il suffit de créer une classe implémentant PaymentMethod, sans toucher au code existant.
3. Liskov Substitution Principle (LSP) — principe de substitution de Barbara Liskov
Essence du principe
Les objets d'un programme doivent pouvoir être remplacés par des instances de leurs sous-types sans altérer la correction du programme. En termes simples, si vous avez une classe Bird et une sous-classe Penguin, le code qui fonctionne avec Bird ne doit pas se casser lorsqu'on lui substitue Penguin.
Mauvais exemple (violation du LSP)
open class Rectangle { open var width: Int = 0