Einführung in die SOLID-Prinzipien in Swift
\nJeder iOS-Entwickler stößt früher oder später auf das Problem des „Spaghetti-Codes“: Klassen schwellen an, Änderungen an einer Stelle bringen die gesamte Anwendung zum Absturz, und das Testen wird zum Albtraum. Die Lösung dieser Probleme liegt im Kern der objektorientierten Softwareentwicklung – den SOLID-Prinzipien. Diese fünf von Robert Martin formulierten Regeln helfen dabei, flexiblen, wartbaren und skalierbaren Code in Swift zu erstellen. In diesem Artikel werden wir jedes Prinzip von der Theorie bis zu praktischen Beispielen für iOS/macOS durchgehen.
\nUnabhängig davon, ob Sie mit UIKit, SwiftUI oder AppKit schreiben, SOLID ist ein universelles Fundament. Lassen Sie uns in die Details eintauchen.
\n\n1. Single Responsibility Principle (SRP) – Prinzip der einzigen Verantwortung
\nTheorie
\nEine Klasse sollte nur einen einzigen Grund für eine Änderung haben. Mit anderen Worten, jedes Modul ist für genau eine Funktionalität verantwortlich. Wenn Ihr ViewController die UI verwaltet, Netzwerkanfragen verarbeitet und JSON parst, verstößt er gegen das SRP.
\nPraxis in Swift
\nBetrachten wir ein typisches Beispiel – das Laden und Anzeigen von Benutzerdaten.
\n// ❌ Schlecht: Verstoß gegen SRP\nclass UserViewController: UIViewController {\n func loadUser() {\n // Netzwerkanfrage\n let url = URL(string: "https://api.example.com/user")!\n URLSession.shared.dataTask(with: url) { data, _, _ in\n let user = try? JSONDecoder().decode(User.self, from: data!)\n DispatchQueue.main.async {\n self.updateUI(with: user)\n }\n }.resume()\n }\n \n func updateUI(with user: User?) {\n // UI aktualisieren\n }\n}\n// ✅ Gut: Verantwortung trennen\nclass UserService {\n func fetchUser(completion: @escaping (User?) -> Void) {\n let url = URL(string: "https://api.example.com/user")!\n URLSession.shared.dataTask(with: url) { data, _, _ in\n let user = try? JSONDecoder().decode(User.self, from: data!)\n completion(user)\n }.resume()\n }\n}\n\nclass UserViewController: UIViewController {\n let userService = UserService()\n \n func loadUser() {\n userService.fetchUser { [weak self] user in\n DispatchQueue.main.async {\n self?.updateUI(with: user)\n }\n }\n }\n \n private func updateUI(with user: User?) {\n // UI aktualisieren\n }\n}\nFazit: Jetzt ist UserService nur für das Netzwerk zuständig und UserViewController nur für die UI. Änderungen an der Netzwerklogik betreffen den Controller nicht.
2. Open-Closed Principle (OCP) – Prinzip der Offenheit/Geschlossenheit
\nTheorie
\nKlassen sollten für Erweiterungen offen, aber für Modifikationen geschlossen sein. Anstatt vorhandenen Code zu ändern, fügen wir neuen Code durch Vererbung oder Protokolle hinzu. In Swift sind Protokolle und Erweiterungen das ideale Werkzeug dafür.
\nPraxis in Swift
\nStellen Sie sich vor, wir schreiben ein Modul zur Berechnung von Rabatten in einem Online-Shop.
\n// ❌ Schlecht: Beim Hinzufügen eines neuen Rabatts muss die Klasse geändert werden\nclass DiscountCalculator {\n func calculate(price: Double, type: String) -> Double {\n if type == "seasonal" {\n return price * 0.9\n } else if type == "loyalty" {\n return price * 0.85\n }\n return price\n }\n}\n// ✅ Gut: Verwenden eines Protokolls zur Erweiterung\nprotocol DiscountStrategy {\n func applyDiscount(to price: Double) -> Double\n}\n\nstruct SeasonalDiscount: DiscountStrategy {\n func applyDiscount(to price: Double) -> Double {\n price * 0.9\n }\n}\n\nstruct LoyaltyDiscount: DiscountStrategy {\n func applyDiscount(to price: Double) -> Double {\n price * 0.85\n }\n}\n\nclass DiscountCalculator {\n func calculate(price: Double, strategy: DiscountStrategy) -> Double {\n strategy.