Patrons de conception en Kotlin avec exemples : niveau avancé
Au niveau débutant, les développeurs maîtrisent les patrons classiques : Singleton, Factory, Observer. Cependant, lors de la construction de systèmes complexes et évolutifs, un réglage architectural plus fin est nécessaire. Dans cet article, nous allons analyser quatre patrons de conception avancés en Kotlin : Composite, Visiteur, État et Pont. Chaque patron sera illustré par un exemple de code pratique et une explication du moment où il convient de l'utiliser.
1. Patron Composite (Composite)
Le patron Composite permet aux clients de travailler de manière uniforme avec des objets individuels et leurs groupes. C'est une solution idéale pour construire des structures hiérarchiques, telles que des systèmes de fichiers, des menus d'interface ou des scènes graphiques.
En Kotlin, nous utilisons souvent des classes sealed pour représenter les nœuds d'un arbre. Considérons un exemple de construction d'une structure "Fichiers et Dossiers" :
sealed class FileSystemNode(open val name: String) {
data class File(override val name: String, val size: Long) : FileSystemNode(name)
data class Directory(
override val name: String,
val children: MutableList<FileSystemNode> = mutableListOf()
) : FileSystemNode(name) {
fun add(node: FileSystemNode) {
children.add(node)
}
fun remove(node: FileSystemNode) {
children.remove(node)
}
fun getTotalSize(): Long = children.sumOf {
when (it) {
is File -> it.size
is Directory -> it.getTotalSize()
}
}
}
}
// Utilisation
fun main() {
val root = FileSystemNode.Directory("root")
root.add(FileSystemNode.File("readme.txt", 1024))
val subDir = FileSystemNode.Directory("src")
subDir.add(FileSystemNode.File("main.kt", 4096))
root.add(subDir)
println("Taille totale : ${root.getTotalSize()} octets") // 5120
}
Avantages : simplifie le code client, prend en charge les opérations récursives. Inconvénients : peut complexifier le système si l'arbre est trop profond.
2. Patron Visiteur (Visitor)
Le Visiteur permet d'ajouter de nouvelles opérations à des objets sans modifier leurs classes. C'est particulièrement utile lorsque vous avez une hiérarchie de classes stable, mais des algorithmes de traitement qui changent fréquemment.
En Kotlin, il fonctionne très bien avec les classes sealed. Implémentons un exemple de calcul de surface pour des formes :
sealed class Shape
class Circle(val radius: Double) : Shape()
class Rectangle(val width: Double, val height: Double) : Shape()
interface ShapeVisitor<T> {
fun visit(circle: Circle): T
fun visit(rectangle: Rectangle): T
}
class AreaCalculator : ShapeVisitor<Double> {
override fun visit(circle: Circle) = Math.PI * circle.radius * circle.radius
override fun visit(rectangle: Rectangle) = rectangle.width * rectangle.height
}
// Utilisation
fun <T> Shape.accept(visitor: ShapeVisitor<T>): T = when (this) {
is Circle -> visitor.visit(this)
is Rectangle -> visitor.visit(this)
}
fun main() {
val shapes = listOf(Circle(5.0), Rectangle(3.0, 4.0))
val calculator = AreaCalculator()
shapes.forEach { println("Surface : ${it.accept(calculator)}") }
}
Quand l'utiliser : si la structure des objets change rarement, mais que les opérations sur ceux-ci changent fréquemment. En Kotlin, on peut utiliser when pour les cas simples, mais le Visitor reste indispensable pour une logique complexe avec de nombreux types.
3. Patron État (State)
Le patron État permet à un objet de modifier son comportement en fonction de son état interne. C'est une alternative élégante aux if-else ou switch-case volumineux.
En Kotlin, nous pouvons utiliser des classes sealed pour représenter les états. Exemple — une commande dans une boutique en ligne