Entwurfsmuster in Kotlin mit Beispielen: Fortgeschrittenes Niveau
Auf dem Anfängerniveau lernen Entwickler die klassischen Entwurfsmuster: Singleton, Factory, Observer. Beim Aufbau komplexer, skalierbarer Systeme ist jedoch eine feinere architektonische Abstimmung erforderlich. In diesem Artikel analysieren wir vier fortgeschrittene Entwurfsmuster in Kotlin: Composite, Visitor, State und Bridge. Jedes Muster wird mit einem praktischen Codebeispiel und einer Erläuterung veranschaulicht, wann es angewendet werden sollte.
1. Composite-Muster
Das Composite-Muster ermöglicht es Clients, einheitlich mit einzelnen Objekten und deren Gruppen zu arbeiten. Es ist eine ideale Lösung für den Aufbau hierarchischer Strukturen wie Dateisysteme, Interface-Menüs oder grafische Szenen.
In Kotlin verwenden wir oft sealed classes zur Darstellung von Baumknoten. Betrachten wir ein Beispiel für den Aufbau einer "Dateien und Ordner"-Struktur:
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() } } }}
// Verwendungfun 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("Gesamtgröße: ${root.getTotalSize()} Bytes") // 5120}Vorteile: Vereinfacht den Client-Code, unterstützt rekursive Operationen. Nachteile: Kann das System verkomplizieren, wenn der Baum zu tief ist.
2. Visitor-Muster
Der Visitor ermöglicht das Hinzufügen neuer Operationen zu Objekten, ohne deren Klassen zu ändern. Dies ist besonders nützlich, wenn Sie eine stabile Klassenhierarchie, aber häufig wechselnde Verarbeitungsalgorithmen haben.
In Kotlin funktioniert dies hervorragend mit sealed classes. Implementieren wir ein Beispiel zur Berechnung der Fläche für Formen:
sealed class Shapeclass 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}
// Verwendungfun <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("Fläche: ${it.accept(calculator)}") }}Wann anwenden: Wenn sich die Objektstruktur selten ändert, die Operationen darauf jedoch häufig. In Kotlin kann für einfache Fälle when verwendet werden, aber Visitor bleibt bei komplexer Logik mit vielen Typen unverzichtbar.
3. State-Muster
Das State-Muster ermöglicht es einem Objekt, sein Verhalten abhängig vom internen Zustand zu ändern. Dies ist eine elegante Alternative zu umfangreichen if-else- oder switch-case-Konstrukten.
In Kotlin können wir sealed classes zur Darstellung von Zuständen verwenden. Beispiel — eine Bestellung in einem Online-Shop