Introduction aux patrons de conception en Kotlin
Les patrons de conception (design patterns) sont des solutions éprouvées pour des problèmes de développement courants. En langage Kotlin, de nombreux patrons classiques sont implémentés de manière plus concise et plus sûre grâce aux fonctionnalités intégrées : null-sécurité, fonctions d'extension, coroutines et un système de types puissant.
Dans cet article, nous allons analyser trois groupes de patrons : les patrons de création, les patrons structurels et les patrons comportementaux — avec des exemples concrets en Kotlin. Vous apprendrez à les utiliser dans le développement quotidien pour créer une architecture flexible et maintenable.
Patrons de création (Creational)
1. Singleton (Singleton)
En Kotlin, le singleton est implémenté avec le mot-clé object. C'est un singleton paresseux et thread-safe sans code superflu.
object DatabaseConnection { private val url = "jdbc:postgresql://localhost:5432/mydb" private val user = "admin" private val password = "secret"
fun connect(): String { return "Connected to $url as $user" }}
fun main() { println(DatabaseConnection.connect()) // Vérifions qu'il s'agit du même objet val ref1 = DatabaseConnection val ref2 = DatabaseConnection println(ref1 === ref2) // true}Avantage : pas besoin d'écrire synchronized ou volatile — Kotlin garantit une instance unique.
2. Factory Method (Méthode de fabrique)
Permet de déléguer la création d'objets aux sous-classes. En Kotlin, il est pratique d'utiliser sealed class et companion object.
sealed class Notification { data class Email(val to: String, val subject: String) : Notification() data class SMS(val phone: String, val message: String) : Notification() data class Push(val deviceId: String, val title: String) : Notification()
companion object Factory { fun createEmail(to: String, subject: String): Notification = Email(to, subject) fun createSMS(phone: String, message: String): Notification = SMS(phone, message) fun createPush(deviceId: String, title: String): Notification = Push(deviceId, title) }}
fun send(notification: Notification) { when (notification) { is Notification.Email -> println("Email to ${notification.to}") is Notification.SMS -> println("SMS to ${notification.phone}") is Notification.Push -> println("Push to ${notification.deviceId}") }}
fun main() { val email = Notification.createEmail("user@example.com", "Hello") send(email)}3. Builder (Constructeur)
En Kotlin, le Builder peut être remplacé par des arguments nommés et des valeurs par défaut. Mais pour les objets complexes avec validation, le Builder classique reste pertinent.
class Pizza private constructor( val size: String, val cheese: Boolean, val pepperoni: Boolean, val mushrooms: Boolean) { class Builder { private var size: String = "Medium" private var cheese: Boolean = false private var pepperoni: Boolean = false private var mushrooms: Boolean = false
fun size(size: String) = apply { this.size = size } fun cheese() = apply { this.cheese = true } fun pepperoni() = apply { this.pepperoni = true } fun mushrooms() = apply { this.mushrooms = true }
fun build(): Pizza { require(size in listOf("Small", "Medium", "Large")) { "Invalid size" } return Pizza(size, cheese, pepperoni, mushrooms) } }}
fun main() { val pizza = Pizza.Builder() .size("Large") .cheese() .pepperoni() .build() println("Pizza: ${pizza.size}, cheese=${pizza.cheese}, pepperoni=${pizza.pepperoni}")}Patrons structurels (Structural)
4. Adapter (Adaptateur)
L'adaptateur permet à des objets avec