SOLID-Prinzipien in Kotlin: Theorie und Praxis auf fortgeschrittenem Niveau
SOLID ist ein grundlegender Satz von Prinzipien des objektorientierten Designs, eingeführt von Robert Martin. Im Kontext von Kotlin, das die besten Eigenschaften von OOP und funktionaler Programmierung vereint, erhalten diese Prinzipien neue Möglichkeiten für die Umsetzung. In diesem Artikel betrachten wir jedes Prinzip auf fortgeschrittenem Niveau und nutzen dabei die einzigartigen Sprachfunktionen: Inline-Funktionen, Delegation, Sealed-Klassen und Extension-Funktionen.
1. Single Responsibility Principle (SRP) und Delegation
Das Prinzip der einzigen Verantwortung besagt: Eine Klasse sollte nur einen Grund für eine Änderung haben. In Kotlin wird zur Einhaltung des SRP oft das Delegationsmuster (Delegation) verwendet. Das Schlüsselwort by ermöglicht es, die Implementierung eines Interfaces automatisch an ein anderes Objekt zu delegieren.
Problem: Die Klasse UserService ist sowohl für die Geschäftslogik als auch für das Logging und den E-Mail-Versand verantwortlich.
// ❌ Verletzung des SRPclass UserService { fun registerUser(email: String, password: String) { // Geschäftslogik println("Benutzer registriert") // Logging log("Benutzer registriert: $email") // E-Mail-Versand sendEmail(email, "Willkommen!") }
private fun log(message: String) { /* ... */ } private fun sendEmail(to: String, body: String) { /* ... */ }}Lösung mit Delegation:
// ✅ Einhaltung des SRP durch Delegationinterface Logger { fun log(message: String)}
interface EmailSender { fun send(to: String, body: String)}
class ConsoleLogger : Logger { override fun log(message: String) = println("LOG: $message")}
class SmtpEmailSender : EmailSender { override fun send(to: String, body: String) = println("Sende E-Mail an $to: $body")}
class UserService( private val logger: Logger, private val emailSender: EmailSender) : Logger by logger, EmailSender by emailSender { // 👈 Delegation
fun registerUser(email: String, password: String) { println("Benutzer registriert") log("Benutzer registriert: $email") send(email, "Willkommen!") }}
// Verwendungfun main() { val service = UserService(ConsoleLogger(), SmtpEmailSender()) service.registerUser("user@example.com", "pass123")}Die Verwendung von by vermeidet Boilerplate-Code und trennt die Zuständigkeiten klar. Die Klasse UserService ist jetzt nur noch für die Registrierung zuständig, während Logging und Versand an separate Klassen delegiert werden.
2. Open-Closed Principle (OCP) und Sealed-Klassen
Das Prinzip der Offenheit/Geschlossenheit: Klassen sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. In Kotlin eignen sich dafür ideal Sealed-Klassen und Extension-Funktionen.
Problem: Die Funktion processPayment verwendet when mit Typprüfung, und beim Hinzufügen einer neuen Zahlungsmethode muss sie modifiziert werden.
// ❌ Verletzung des OCPfun processPayment(type: String, amount: Double) { when (type) { "card" -> println("Verarbeite Kartenzahlung: $amount") "paypal" -> println("Verarbeite PayPal-Zahlung: $amount") else -> throw IllegalArgumentException("Unbekannte Zahlungsart") }}Lösung mit Sealed-Klassen:
// ✅ Einhaltung des OCPsealed class PaymentMethod { data class Card(val number: String, val cvv: String) : PaymentMethod() data class PayPal(val email: String) : PaymentMethod() object Cash : PaymentMethod()}
// Funktion ist für Modifikationen geschlossenfun processPayment(method: PaymentMethod, amount: Double) { // Wenn neue Unterklassen hinzukommen, verlangt der Compiler deren Behandlung