Separation of Concerns ist ein Prinzip, bei dem jedes Modul oder jede Schicht einer Anwendung für einen Verantwortungsbereich zuständig ist. Laut Wikipedia wurde der Begriff 1974 von Edsger Dijkstra eingeführt und ist seitdem zur Grundlage der Softwarearchitektur geworden. Die Trennung von Verantwortlichkeiten ermöglicht es Entwicklern, eine Code-Schicht zu ändern, ohne andere zu beeinträchtigen, was in mobilen Projekten mit langen Support-Zyklen von entscheidender Bedeutung ist.
Wichtige Erkenntnisse
Separation of Concerns ist ein Prinzip der Zerlegung eines Softwaresystems in unabhängige Teile, von denen jeder eine Aufgabe löst. Der Begriff Concern (Verantwortungsbereich) bezeichnet jeden trennbaren Teil der Funktionalität: Bildschirmanzeige, Tippverarbeitung, Datenvalidierung oder Netzwerkkommunikation. Das Prinzip schreibt vor, Code so zu gruppieren, dass Änderungen in einem Bereich keine Änderungen in anderen erfordern.
In der mobilen Entwicklung zeigt sich SoC auf mehreren Ebenen: von der Aufteilung einer Anwendung in Bildschirme bis zur Organisation von Code innerhalb einer einzigen Klasse. Eine Activity oder ViewController, die gleichzeitig Daten aus dem Netzwerk lädt, JSON parst und die UI rendert, verstößt gegen Separation of Concerns — solcher Code ist schwer zu warten, zu testen und zu erweitern. Die Alternative besteht darin, jede Verantwortlichkeit in eine separate Komponente auszulagern.
Das Prinzip ist eng mit dem Konzept der Abstraktion verbunden: Jede Schicht bietet eine streng definierte Schnittstelle und verbirgt Implementierungsdetails. Dadurch kann ein Entwickler eine Netzwerkbibliothek oder Datenbank austauschen, ohne die UI-Logik neu schreiben zu müssen. Dies ist besonders wertvoll in langlebigen Projekten, in denen sich Anforderungen und Technologien im Laufe der Zeit ändern.
Edsger Dijkstra formulierte die Idee von Separation of Concerns erstmals in seinem Artikel von 1974 „On the Role of Scientific Thought“. Er argumentierte, dass die Komplexität von Softwaresystemen kontrolliert werden kann, indem man sie in Teile zerlegt, die isoliert analysiert werden. Dieser Ansatz stand im Gegensatz zu den monolithischen Programmen der damaligen Zeit, bei denen Code Berechnung, Ein-/Ausgabe und Benutzeroberfläche vermischte.
In den 1980er Jahren wurde die Idee von Befürwortern der strukturierten Programmierung und später vom objektorientierten Ansatz weiterentwickelt. Sprachen wie Smalltalk und C++ boten Kapselungs- und Modularitätsmechanismen, die SoC zu einem praktischen Werkzeug machten. Moderne Architekturmuster — MVC, MVP, MVVM und Clean Architecture — sind direkte Verkörperungen des Separation-of-Concerns-Prinzips.
In der Welt der mobilen Entwicklung förderte Apple MVC als Standard für iOS, bei dem Model-View-Controller Daten, Anzeige und Steuerungslogik trennt. Google schlug für Android Architekturrichtlinien vor, die auf ViewModel und Repository basieren — jede Komponente löst ihre eigene begrenzte Aufgabe. Ohne SoC werden mobile Anwendungen zu Massive View Controller — Klassen mit Tausenden von Zeilen, bei denen jede Änderung riskiert, die gesamte Funktionalität zu zerstören.
Vier Hauptschichten bilden eine typische mobile Anwendungsarchitektur, die Separation of Concerns implementiert. Jede Schicht ist nur für ihre Domäne verantwortlich und interagiert mit Nachbarn über Schnittstellen.
View ist ausschließlich für die Anzeige von Daten und die Verarbeitung von Benutzerereignissen verantwortlich. Unter iOS sind dies UIViewController und UIView, unter Android — Fragment oder Activity. ViewModel enthält den Bildschirmzustand und die Logik zur Umwandlung von Daten in ein anzeigebereites Format. Die Trennung stellt sicher, dass das Ersetzen von UIKit durch SwiftUI oder das Umschreiben eines Bildschirms mit Jetpack Compose die Geschäftslogik nicht beeinträchtigt.
Das Testen von ViewModel erfordert weder Emulator noch Simulator — Unit-Tests, die die Datenumwandlung und Reaktion auf Benutzeraktionen überprüfen, sind ausreichend. Dies ist eine direkte Folge von Separation of Concerns: UI wird nicht mit Geschäftsregeln vermischt, und jede Komponente wird isoliert getestet.
Use Case (oder Interactor) enthält die Geschäftsregeln der Anwendung — Berechnungen, Validierungen, Orchestrierung von Datenaufrufen. Diese Schicht weiß nichts von der Existenz der UI oder von Plattform-Frameworks. Der Use Case erhält Daten vom Repository, wendet Logik darauf an und gibt das fertige Ergebnis an ViewModel zurück. Die Trennung ermöglicht die Wiederverwendung eines Use Case auf verschiedenen Bildschirmen.
Zum Beispiel überprüft LoginUseCase die Gültigkeit der E-Mail, ruft AuthRepository zur Authentifizierung auf und gibt das Ergebnis zurück. Es hängt nicht davon ab, wie der Login-Bildschirm aussieht — SwiftUI, UIKit oder Compose. Wenn sich die Geschäftsregeln ändern, reicht es aus, einen Use Case zu ändern, ohne die UI oder Datenbank zu berühren.
Repository abstrahiert die Datenquellen: entfernte API, lokale Datenbank oder In-Memory-Cache. ViewModel und Use Case wissen nicht, woher die Daten genau kommen — Repository entscheidet, ob aus dem Netzwerk oder aus dem Cache geladen wird. Diese Trennung ermöglicht es, die Speicherimplementierung zu ändern, ohne die Geschäftslogik oder die UI zu beeinträchtigen.
DataSource ist eine noch niedrigere Trennung: NetworkDataSource ist nur für HTTP-Anfragen zuständig, LocalDataSource — für die Arbeit mit Room oder CoreData. Repository kombiniert Aufrufe an verschiedene DataSources zu einer einzigen kohärenten Schnittstelle. Jede DataSource wird unabhängig mit Mocks oder Fake-Servern getestet.
Die korrekte Implementierung der DataSource-Schicht stellt sicher, dass eine Änderung des Datenbankschemas oder der Ersatz von REST API durch GraphQL nur eine DataSource betrifft, nicht jedoch das Repository oder seine Verbraucher. Dies ist eine direkte Folge von Separation of Concerns auf der Infrastrukturebene: Jede technische Belang wird isoliert und ohne kaskadierende Änderungen austauschbar.
MVVM (Model-View-ViewModel) ist das beliebteste Muster für die mobile Entwicklung und implementiert Separation of Concerns direkt. Model enthält Daten und Geschäftslogik, View ist für die Anzeige verantwortlich, und ViewModel verbindet sie über reaktive Mechanismen. In Flutter übernimmt BLoC eine ähnliche Rolle mit Trennung in Ereignisse, Zustände und Geschäftslogik.
Clean Architecture von Robert Martin (Uncle Bob) führt SoC zum Maximum: Das System wird in unabhängige Ringe unterteilt — Entitäten, Use Cases, Adapter und Frameworks. Innere Ringe (Entitäten) hängen nicht von äußeren Ringen (Frameworks) ab. Dies ermöglicht es, die Datenbank, das UI-Framework und sogar die Plattform zu ändern, ohne die Kernlogik der Anwendung neu schreiben zu müssen.
In der Praxis implementieren mobile Projekte selten die vollständige Clean Architecture — für die meisten Anwendungen reicht eine Drei-Schichten-Architektur aus: UI, Domain und Data. Die Domain-Schicht enthält Use Cases und Geschäftsmodelle und ist vollständig von Android SDK oder iOS SDK isoliert. Eine solche Trennung bringt 80% des Nutzens bei 20% des Aufwands.
// Data layer — nur für das Abrufen von Daten zuständig
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): User = api.fetchUser(id)
}
// Domain layer — Geschäftslogik, kennt weder API noch Datenbank
class GetUserNameUseCase(
private val repo: UserRepository
) {
suspend fun invoke(id: String): String {
val user = repo.getUser(id)
return "${user.firstName} ${user.lastName}"
}
}
// UI layer — nur Anzeige
class UserViewModel(
private val getUserName: GetUserNameUseCase
) {
fun onUserLoaded(id: String) {
viewModelScope.launch {
_name.value = getUserName.invoke(id)
}
}
}
Der obige Code demonstriert eine reine Trennung: UserRepository arbeitet nur mit der API, GetUserNameUseCase enthält die Geschäftslogik zur Namensformatierung, und UserViewModel verwaltet den UI-Zustand. Jede Klasse hat einen Grund zur Änderung, was das Wesen von Separation of Concerns ist.
Der Hauptvorteil von SoC ist die Wartbarkeit. Code, der in unabhängige Schichten unterteilt ist, lässt sich leichter analysieren: Der Entwickler betrachtet nur die Schicht, in der der Fehler auftritt, und wird nicht durch andere abgelenkt. In langfristigen Projekten reduziert dies die Zeit für die Fehlersuche und -behebung um 30–50% im Vergleich zu monolithischem Code.
Der zweite wichtige Vorteil ist die Testbarkeit. Wenn die Geschäftslogik von UI und Frameworks isoliert ist, wird sie durch Unit-Tests ohne Emulator-Lauf abgedeckt. Android- und iOS-Projekte mit hoher Unit-Test-Abdeckung haben deutlich weniger Regressionen beim Hinzufügen neuer Funktionen.
Die Hauptgrenze ist die erhöhte Komplexität. Übermäßige Fragmentierung in Mikroschichten und Abstraktionen führt dazu, dass ein Entwickler für das Hinzufügen eines einfachen Buttons fünf Dateien bearbeiten muss. Das Prinzip Separation of Concerns erfordert eine vernünftige Balance: Trennen Sie nur die Bereiche, die sich tatsächlich unabhängig voneinander ändern. Für kleine Projekte reicht eine grundlegende Trennung in UI, Logik und Daten ohne zusätzliche Abstraktionen aus.
Häufig gestellte Fragen
SoC ist ein Prinzip der Trennung nach Verantwortungsbereichen, während Modularität eine Möglichkeit ist, Code in physische Module zu organisieren. SoC kann innerhalb eines einzelnen Moduls durch Schichten oder Klassen implementiert werden, während Modularität eine Aufteilung in unabhängige Builds erfordert.
SoC ist ein übergeordnetes Konzept zu den SOLID-Prinzipien. Das Single-Responsibility-Prinzip (S) ist SoC auf der Ebene einer einzelnen Klasse. Das Dependency-Inversion-Prinzip (D) hilft, SoC zwischen Schichten durch Schnittstellen und Abhängigkeitsinjektion zu implementieren.
Ja, aber in moderatem Umfang. Für eine einfache Anwendung reicht es, UI und Geschäftslogik zu trennen. Eine übermäßige Anzahl von Schichten wird den Code ohne praktischen Nutzen verkomplizieren. Mit dem Wachstum des Projekts wird die Anzahl der Schichten schrittweise erhöht.
Es gibt keine direkten Auswirkungen auf die Leistung — SoC betrifft die Code-Architektur, nicht die Ausführung. Allerdings kann die Aufteilung in Schichten aufgrund zusätzlicher Aufrufe zwischen den Schichten einen indirekten Overhead verursachen. In der Praxis ist dieser Aufwand im Vergleich zu den Vorteilen der Wartbarkeit vernachlässigbar.
Dependency Injection (Hilt, Koin, Swinject) verwaltet explizit die Grenzen zwischen Schichten. Architektur-Linter-Regeln in Detekt (Android) und SwiftLint (iOS) verbieten Importe aus nicht erlaubten Schichten. Git-Hooks können überprüfen, dass die Geschäftsschicht keine UI-Bibliotheken importiert.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch