Patrons de conception en Kotlin avec exemples
Les patrons de conception sont des solutions éprouvées pour résoudre des problèmes typiques lors du développement de logiciels. Ils aident à créer une architecture flexible, évolutive et maintenable. En langage Kotlin, grâce à sa syntaxe concise et à ses capacités fonctionnelles, de nombreux patrons classiques sont implémentés de manière particulièrement élégante. Dans cet article, nous allons analyser trois patrons clés : Singleton, Factory Method et Observer, et montrer leur implémentation en Kotlin avec des exemples pratiques.
1. Singleton
Le patron Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global à celle-ci. En Kotlin, il existe une construction intégrée pour cela — object. Elle crée un singleton thread-safe sans code superflu.
Implémentation en Kotlin
La façon la plus simple de déclarer un singleton est d'utiliser le mot-clé object :
object DatabaseConfig { private val url = "jdbc:postgresql://localhost:5432/mydb" private val username = "admin" private val password = "secret"
fun connect(): String { return "Connexion à $url en tant que $username" }}
fun main() { println(DatabaseConfig.connect()) // Sortie : Connexion à jdbc:postgresql://localhost:5432/mydb en tant que admin}Si vous avez besoin d'un Singleton classique avec un constructeur privé (par exemple, pour hériter d'une autre classe), utilisez companion object :
class Logger private constructor() { fun log(message: String) { println("[LOG]: $message") }
companion object { private val instance: Logger by lazy { Logger() }
fun getInstance(): Logger { return instance } }}
fun main() { val logger = Logger.getInstance() logger.log("Application démarrée")}L'avantage de l'approche avec by lazy est l'initialisation paresseuse, qui n'est effectuée qu'à la première utilisation.
2. Factory Method (Méthode de fabrique)
Le patron Factory Method définit une interface pour créer un objet, mais permet aux sous-classes de modifier le type d'objet créé. En Kotlin, cela est souvent implémenté via sealed class ou des interfaces.
Implémentation en Kotlin
Supposons que nous développions un système de notifications. Nous avons différents types de notifications : Email, SMS et Push. Créons une fabrique pour les générer.
// Interface pour toutes les notificationsinterface Notification { fun send(message: String)}
// Implémentations concrètesclass EmailNotification : Notification { override fun send(message: String) { println("Envoi d'Email : $message") }}
class SMSNotification : Notification { override fun send(message: String) { println("Envoi de SMS : $message") }}
class PushNotification : Notification { override fun send(message: String) { println("Envoi de notification Push : $message") }}
// Fabriqueobject NotificationFactory { fun createNotification(type: String): Notification { return when (type.lowercase()) { "email" -> EmailNotification() "sms" -> SMSNotification() "push" -> PushNotification() else -> throw IllegalArgumentException("Type de notification inconnu : $type") } }}
fun main() { val notification = NotificationFactory.createNotification("sms") notification.send("Votre code de confirmation : 1234") // Sortie : Envoi de SMS : Votre code de confirmation : 1234}Si le nombre de types est fixe, il est préférable d'utiliser sealed class pour une plus grande sécurité :
sealed class NotificationType { object Email : NotificationType() object SMS : NotificationType() object Push : NotificationType()}
fun createNotification(type: NotificationType): Notificat