Android Coden
Android 4 min lesen

Separation of Concerns – Verantwortlichkeiten klar trennen

Separation of Concerns trennt UI, Geschäftslogik und Datenzugriff klar voneinander. So bleibt Android-Code testbar, wartbar und strukturiert.

Wenn UI-Code Datenbankabfragen startet und Geschäftslogik mitten im Fragment lebt, wächst eine App schnell zu einem unwartbaren Knäuel heran. Separation of Concerns – kurz SoC – ist das Architekturprinzip, das genau das verhindert: Jede Klasse, jede Schicht, jedes Modul trägt nur die Verantwortung, für die es entworfen wurde.

Was ist das?

Separation of Concerns ist ein Softwareentwurfsprinzip, das fordert, ein System in klar abgegrenzte Teile aufzuteilen, von denen jeder eine einzelne, kohärente Aufgabe übernimmt. Im Android-Kontext bedeutet das konkret: Die Benutzeroberfläche zeigt Daten an und nimmt Nutzereingaben entgegen, die Geschäftslogik entscheidet, was mit diesen Daten passiert, und die Datenschicht kümmert sich darum, wo und wie Daten gespeichert oder abgerufen werden.

Das klingt offensichtlich – und genau das ist das Problem. In der Praxis verleitet der Entwicklungsalltag dazu, alles an einem Ort zu bündeln: eine Activity, die Netzwerkaufrufe startet, UI-Zustand verwaltet und gleichzeitig Geschäftsregeln implementiert. Solche God-Objects entstehen nicht aus böser Absicht, sondern aus dem Wunsch, schnell ein Ergebnis zu sehen. SoC ist die bewusste Entscheidung, dieser Versuchung zu widerstehen und Grenzen zwischen den Zuständigkeiten konsequent zu ziehen.

Die drei Schlüsselbegriffe dieses Themas helfen dabei, die Idee zu verankern: Responsibilities (Zuständigkeiten) beschreiben, welche Aufgabe eine Komponente besitzt. Boundaries (Grenzen) legen fest, was eine Komponente nicht tun darf. Cohesion (Kohäsion) misst, wie eng zusammengehörig die Dinge innerhalb einer Komponente sind – hohe Kohäsion ist das Ziel.

Wie funktioniert es?

Google empfiehlt für moderne Android-Apps eine mehrschichtige Architektur mit mindestens drei Ebenen, die jeweils klar getrennte Aufgaben tragen.

UI-Schicht

Composables, Fragments und Activities stellen Daten dar und leiten Nutzereingaben als Events weiter. Sie enthalten keine Logik außer derjenigen, die unmittelbar mit der Darstellung zusammenhängt – beispielsweise das Umformatieren eines Datums für die Anzeige.

ViewModel-Schicht

ViewModels halten den UI-Zustand als StateFlow oder LiveData und koordinieren bei Bedarf Use Cases. Sie wissen weder, wie Daten intern gespeichert werden, noch, wie sie auf dem Bildschirm gerendert werden. Das macht sie unabhängig vom Android-Framework und damit einfach zu testen.

Datenschicht

Repositories bieten eine einheitliche API für den Datenzugriff – unabhängig davon, ob die Daten aus einer Room-Datenbank, einem Retrofit-Endpunkt oder einem lokalen Cache stammen. Der Rest der App ruft ausschließlich das Repository auf; wie die Daten intern beschafft werden, bleibt hinter dieser Grenze verborgen.

Das Ergebnis ist eine gerichtete Abhängigkeitskette: Die UI kennt nur das ViewModel, das ViewModel kennt nur Repositories oder Use Cases, und die Repositories kennen die konkreten Datenquellen. Keine Schicht kommuniziert an einer anderen vorbei.

In der Praxis

Betrachte ein konkretes Szenario: Eine App lädt eine Liste von Beiträgen aus dem Netz, filtert nur veröffentlichte heraus und sortiert sie nach Datum.

Falsch – alles im Fragment gebündelt:

class PostListFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            val response = RetrofitClient.api.getPosts()    // Netzwerkaufruf in der UI
            if (response.isSuccessful) {
                val sorted = response.body()!!
                    .filter { it.isPublished }              // Geschäftslogik in der UI
                    .sortedByDescending { it.createdAt }
                adapter.submitList(sorted)
            }
        }
    }
}

Dieser Code ist schwer zu testen, weil Netzwerk, Logik und UI-Update untrennbar verwoben sind. Jede Änderung an der Sortierung berührt das Fragment; jeder Test muss einen echten Retrofit-Client aufbauen.

Richtig – Verantwortlichkeiten klar getrennt:

// Datenschicht: nur Netzwerkzugriff
class PostRepository(private val api: PostApi) {
    suspend fun getPosts(): List<Post> = api.getPosts()
}

// ViewModel: Zustand und Geschäftslogik
class PostListViewModel(private val repo: PostRepository) : ViewModel() {
    val posts: StateFlow<List<Post>> = flow {
        emit(
            repo.getPosts()
                .filter { it.isPublished }
                .sortedByDescending { it.createdAt }
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
}

// Fragment: nur Darstellung
class PostListFragment : Fragment() {
    private val vm: PostListViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            vm.posts.collect { adapter.submitList(it) }
        }
    }
}

Das ViewModel lässt sich jetzt mit einer gefakten Repository-Implementierung vollständig ohne Android-Emulator testen.

Typische Stolperfalle – Context im ViewModel: Es ist verlockend, context.getString(R.string.error_network) direkt im ViewModel aufzurufen, um eine Fehlermeldung zu formatieren. Das klingt harmlos, bindet das ViewModel jedoch an Android-APIs und macht Unit-Tests aufwendig. Verwende stattdessen eine sealed class für Fehlerzustände und überlass es der UI-Schicht, daraus einen lokalisierten String zu machen. Das ViewModel bleibt dadurch frei von Darstellungsdetails.

Fazit

Separation of Concerns ist kein akademisches Ideal, sondern ein täglich anwendbares Werkzeug. Stelle dir beim Schreiben neuen Codes die einfache Frage: Gehört das wirklich hierher? Ein Netzwerkaufruf im Fragment, eine UI-String-Aufbereitung im Repository – beide Fehler sind leicht gemacht und teuer zu korrigieren. Öffne eines deiner bestehenden ViewModels und prüfe, ob es Context-Referenzen, Adapter-Instanzen oder String-Ressourcen enthält. Falls ja, hast du eine konkrete Übungsaufgabe: Verschiebe diese Verantwortung an die richtige Stelle und schreibe anschließend einen Unit-Test für das ViewModel, der komplett ohne Android-Instrumentierung auskommt. Gelingt dir das, hast du Separation of Concerns nicht nur verstanden, sondern gelebt.

Quellen (2)
Redaktion

Geschrieben von

Redaktion

Das Redaktionsteam recherchiert und schreibt Artikel zu aktuellen Themen rund um Tech, Lifestyle und Ratgeber.