Android Coden
Android 8 min lesen

Objektorientierte Prinzipien in Android

Du lernst, wie Kapselung, Abstraktion und Polymorphie Android-Code klarer und testbarer machen.

Objektorientierte Prinzipien helfen dir, Android-Code in klare Verantwortlichkeiten zu zerlegen. Dabei geht es nicht darum, für jedes kleine Detail eine neue Basisklasse zu bauen. Es geht darum, Zustände zu schützen, gute Schnittstellen zu entwerfen und verschiedene Implementierungen austauschbar zu halten. In Kotlin, Jetpack und Compose bleibt dieses Denken wichtig, auch wenn du oft mit Funktionen, Datenklassen, Flows und deklarativer UI arbeitest.

Was ist das?

Objektorientierte Prinzipien beschreiben, wie du Software rund um Daten, Verhalten und Verantwortung strukturierst. Die drei Begriffe aus diesem Roadmap-Schritt sind Kapselung, Abstraktion und Polymorphie.

Kapselung bedeutet: Eine Klasse zeigt nach außen nur das, was andere wirklich brauchen. Interne Details bleiben privat. Ein ViewModel kann zum Beispiel intern einen veränderbaren MutableStateFlow halten, aber nach außen nur einen unveränderlichen StateFlow anbieten. Dadurch kann keine UI-Komponente versehentlich den Zustand manipulieren.

Abstraktion bedeutet: Du arbeitest mit einer fachlichen Idee statt mit jeder technischen Einzelheit. Eine App braucht vielleicht “Notizen laden”, aber der aufrufende Code muss nicht wissen, ob die Daten aus Room, Retrofit oder einem Fake für Tests kommen. Eine gute Abstraktion benennt die Absicht klar und versteckt nur die Details, die für den Nutzer der Schnittstelle nicht wichtig sind.

Polymorphie bedeutet: Unterschiedliche Objekte können über dieselbe Schnittstelle verwendet werden. In Kotlin geschieht das oft über Interfaces. Ein NotesRepository kann eine echte Netzwerk-Implementierung, eine lokale Datenbank-Implementierung oder eine Test-Implementierung haben. Der Code, der das Repository nutzt, muss nicht bei jeder Variante umgeschrieben werden.

Im Android-Kontext löst Objektorientierung ein sehr praktisches Problem: Apps wachsen schnell. Screens, Navigation, Datenquellen, Berechtigungen, Fehlerfälle und Tests erzeugen viele Abhängigkeiten. Ohne klare Verantwortlichkeiten landet Logik an zufälligen Orten, etwa direkt in Composables, Activities oder Adapter-Klassen. Dann wird Code schwer testbar und schwer zu ändern. Gute objektorientierte Gestaltung hilft dir, solche Verflechtungen zu vermeiden.

Wie funktioniert es?

Das mentale Modell ist einfach: Eine Klasse sollte eine erkennbare Aufgabe haben, ihre eigenen Daten schützen und mit anderen Teilen der App über klare Schnittstellen sprechen. Du fragst also nicht zuerst: “Welche Vererbungshierarchie brauche ich?” Du fragst: “Welche Verantwortung soll dieser Code übernehmen, und welche Details darf er verbergen?”

Kapselung

Kapselung beginnt mit Sichtbarkeit. Kotlin gibt dir dafür private, internal, protected und public. In Android-Code ist private oft dein wichtigstes Werkzeug. Alles, was nicht von außen gebraucht wird, bleibt privat. Das reduziert die Anzahl der Stellen, an denen ein Objekt in einen ungültigen Zustand gebracht werden kann.

Ein häufiger Fall ist UI-State im ViewModel. Intern darf der Zustand veränderbar sein, weil das ViewModel auf Benutzeraktionen, Repository-Ergebnisse oder Fehler reagieren muss. Die UI sollte diesen Zustand aber nur beobachten. Deshalb veröffentlichst du eine lesbare Sicht und hältst die schreibbare Variante verborgen.

Kapselung ist auch für Qualität wichtig. Wenn du Zustände kontrolliert änderst, kannst du leichter testen, welche Eingabe zu welchem Ergebnis führt. Die Android-Testgrundlagen betonen, dass testbarer Code klare Einheiten und überprüfbares Verhalten braucht. Kapselung hilft dir dabei, solche Einheiten zu formen.

Abstraktion

Abstraktion heißt nicht, alles hinter Interfaces zu verstecken. Eine Abstraktion lohnt sich, wenn sie eine echte Grenze beschreibt: Datenquelle, Systemdienst, Zeit, Zufall, Netzwerk, Datenbank oder eine fachliche Operation. Gute Abstraktionen machen Code lesbarer. Schlechte Abstraktionen verschieben Komplexität nur in zusätzliche Dateien.

In einer Compose-App solltest du zum Beispiel vermeiden, Datenzugriff direkt im Composable zu starten. Ein Composable beschreibt UI aus Zustand. Es sollte nicht wissen müssen, wie ein Token gespeichert wird oder welche HTTP-Route eine Liste lädt. Diese Details gehören eher in Repository-, DataSource- oder Use-Case-Klassen, abhängig von der Architektur deiner App.

Die Entscheidungsregel lautet: Abstrahiere dort, wo du einen Grund hast. Ein Grund kann Testbarkeit sein, etwa wenn du im Unit-Test eine Fake-Implementierung nutzen möchtest. Ein Grund kann Austauschbarkeit sein, etwa wenn du eine lokale Cache-Strategie später ändern willst. Ein Grund kann Lesbarkeit sein, wenn ein technischer Ablauf fachlich klar benannt werden sollte. Kein guter Grund ist: “Vielleicht brauchen wir irgendwann zehn Varianten.”

Polymorphie

Polymorphie entsteht in Kotlin meistens über Interfaces und manchmal über abstrakte Klassen oder sealed Types. Für Android-Lernende ist das Interface oft der beste Einstieg, weil es eine klare Fähigkeit beschreibt, ohne eine gemeinsame Basisklasse zu erzwingen.

Wenn ein ViewModel ein Interface wie NotesRepository bekommt, kann es mit jeder Implementierung arbeiten, die dieses Interface erfüllt. Im Produktionscode ist das vielleicht RoomNotesRepository. Im Test ist es FakeNotesRepository. Der aufrufende Code bleibt stabil. Das macht Tests in lokalen Unit-Tests, instrumentierten Tests und später in Continuous Integration einfacher, weil du Abhängigkeiten gezielt ersetzen kannst.

Wichtig ist: Polymorphie ist kein Selbstzweck. Sie ist nützlich, wenn der aufrufende Code wirklich unabhängig von der konkreten Umsetzung bleiben soll. Wenn es nur eine kleine Datenklasse ohne Verhalten gibt, brauchst du keine abstrakte Basisklasse. Moderne Kotlin- und Android-Projekte kombinieren objektorientierte Prinzipien mit anderen Sprachmitteln: Datenklassen für Werte, sealed Interfaces für begrenzte Zustände, Funktionen für kleine Operationen und Klassen für langlebige Verantwortlichkeiten.

In der Praxis

Stell dir eine kleine Notizen-App vor. Ein Compose-Screen zeigt eine Liste von Notizen. Das ViewModel lädt die Daten und bereitet den UI-State vor. Die eigentliche Datenquelle soll austauschbar bleiben, damit du später Room, Netzwerk oder Tests verwenden kannst.

data class Note(
    val id: String,
    val title: String,
    val body: String
)

interface NotesRepository {
    suspend fun loadNotes(): List<Note>
}

class FakeNotesRepository : NotesRepository {
    override suspend fun loadNotes(): List<Note> = listOf(
        Note(
            id = "1",
            title = "Objektorientierung",
            body = "Kapselung, Abstraktion und Polymorphie gezielt einsetzen."
        )
    )
}

data class NotesUiState(
    val isLoading: Boolean = false,
    val notes: List<Note> = emptyList(),
    val errorMessage: String? = null
)

class NotesViewModel(
    private val repository: NotesRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow(NotesUiState(isLoading = true))
    val uiState: StateFlow<NotesUiState> = _uiState.asStateFlow()

    init {
        viewModelScope.launch {
            runCatching { repository.loadNotes() }
                .onSuccess { notes ->
                    _uiState.value = NotesUiState(notes = notes)
                }
                .onFailure {
                    _uiState.value = NotesUiState(
                        errorMessage = "Notizen konnten nicht geladen werden."
                    )
                }
        }
    }
}

In diesem Beispiel siehst du die drei Prinzipien kompakt zusammen:

Kapselung: _uiState ist privat. Von außen ist nur uiState sichtbar. Die UI kann den Zustand beobachten, aber nicht direkt überschreiben. Dadurch bleibt das ViewModel die zentrale Stelle, die entscheidet, wann geladen, erfolgreich aktualisiert oder ein Fehler angezeigt wird.

Abstraktion: NotesRepository beschreibt, was gebraucht wird: Notizen laden. Es beschreibt nicht, ob die Daten aus einer Datenbank, einer Datei oder einem Webservice kommen. Das ViewModel bleibt dadurch auf seine Aufgabe konzentriert: Zustand für die UI vorbereiten.

Polymorphie: FakeNotesRepository ist eine mögliche Implementierung. Eine spätere RoomNotesRepository-Klasse könnte dasselbe Interface erfüllen. Das ViewModel müsste dafür nicht geändert werden. Genau diese Austauschbarkeit ist im Test wertvoll.

Ein einfacher Unit-Test könnte prüfen, ob das ViewModel nach dem Laden Notizen im State hat. Dafür gibst du ihm eine Fake-Implementierung. Du testest dann nicht Room, Retrofit oder Android-Systemdetails, sondern die Logik deiner Klasse. Das passt gut zur Android-Empfehlung, Tests so zu schneiden, dass sie schnell, zuverlässig und klar im Fehlerbild sind.

Eine typische Stolperfalle ist Überabstraktion. Anfänger bauen manchmal BaseViewModel, BaseRepository, BaseUseCase, AbstractManager und mehrere Vererbungsebenen, bevor ein echtes Problem sichtbar ist. Das wirkt geordnet, macht den Code aber oft schwerer verständlich. Wenn du zum Lesen einer einfachen Funktion erst fünf Elternklassen öffnen musst, ist die Struktur zu schwer.

Eine zweite Stolperfalle ist falsche Kapselung. Manche Klassen machen fast alles public, weil es beim Entwickeln bequem ist. Andere verstecken so viel, dass Tests nur noch über komplizierte Umwege möglich sind. Die bessere Mitte ist: Interne Details bleiben privat, aber beobachtbares Verhalten bleibt gut testbar. Du testest nicht jede private Hilfsfunktion einzeln, sondern die öffentliche Wirkung der Klasse.

Eine dritte Stolperfalle betrifft Compose. Weil Composables schnell geschrieben sind, landet dort leicht Geschäftslogik: Daten laden, Fehler interpretieren, Repository auswählen, Formulare validieren. Kleine UI-nahe Formatierung kann im Composable bleiben. Dauerhafte fachliche Regeln, Datenzugriff und Zustandsübergänge gehören jedoch in passende Klassen oder Funktionen außerhalb der UI. So bleibt die Oberfläche deklarativ und leichter prüfbar.

Für deinen Alltag kannst du dir diese Entscheidungsregel merken: Erstelle eine Klasse oder ein Interface, wenn du eine klare Verantwortung benennen kannst. Benenne sie nach dieser Verantwortung. Vermeide Klassen, deren Name nur eine technische Schublade ist, etwa Helper, Manager oder Util, sofern du genauer sagen kannst, was sie leisten. Ein TokenStore, UserRepository oder DateFormatter ist meist klarer als ein allgemeiner AppHelper.

In Code-Reviews kannst du objektorientierte Qualität mit wenigen Fragen prüfen. Welche Klasse besitzt welchen Zustand? Wer darf diesen Zustand ändern? Welche Schnittstelle braucht der Aufrufer wirklich? Gibt es eine konkrete Implementierung, die unnötig in UI-Code durchsickert? Gibt es eine Vererbung, die durch Komposition oder ein kleines Interface einfacher wäre? Diese Fragen sind oft wirksamer als starre Regeln.

Auch die Release-Praxis profitiert davon. Android-Qualität entsteht nicht nur durch fehlerfreie Screens, sondern durch Code, der sich verlässlich ändern lässt. Wenn Verantwortlichkeiten sauber getrennt sind, kannst du neue Funktionen gezielter testen. In einer Continuous-Integration-Pipeline laufen solche Tests dann regelmäßig und geben dir früher Rückmeldung, wenn eine Änderung Verhalten bricht. Objektorientierte Prinzipien sind also nicht nur Theorie aus dem Software-Engineering, sondern ein Werkzeug für wartbare Android-Apps.

Fazit

Objektorientierte Prinzipien geben dir eine ruhige Struktur für wachsenden Android-Code: Kapselung schützt Zustand, Abstraktion beschreibt Absichten, und Polymorphie macht Implementierungen austauschbar. Nutze diese Prinzipien gezielt, um Verantwortlichkeiten zu modellieren, nicht um möglichst viele Schichten zu erzeugen. Prüfe dein Verständnis praktisch: Nimm ein kleines ViewModel, verstecke veränderbaren State, führe ein sinnvolles Repository-Interface ein, schreibe eine Fake-Implementierung und teste das sichtbare Verhalten. Danach lies den Code im Review-Modus und frage dich, ob jede Klasse eine klare Aufgabe hat und ob eine einfachere Struktur denselben Zweck erfüllen würde.

Quellen (3)
Redaktion

Geschrieben von

Redaktion

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