LoD (Law of Demeter), auch bekannt als Prinzip des geringsten Wissens, ist eine Entwurfsregel, die vorschreibt, dass ein Objekt nur mit seinen unmittelbaren „Freunden“ interagieren darf. Es wurde 1987 an der Northeastern University (Boston) im Rahmen des Demeter-Projekts formuliert. Laut einer Studie ACM Communications (1989) reduziert die Anwendung von LoD die Anzahl der Codeänderungen bei der Modifikation einer Datenstruktur um 35%, da Änderungen nicht über Aufrufketten propagiert werden. LoD ist kein Dogma, sondern ein Schutz vor zerbrechlichem Code.
Wichtige Punkte
LoD (Law of Demeter) oder das Prinzip des geringsten Wissens ist eine Regel, die die Menge der Objekte einschränkt, mit denen ein bestimmtes Objekt interagieren kann. Eine Methode des Objekts M darf nur Methoden aufrufen von: M selbst, den Parametern der Methode, innerhalb von M erstellten Objekten, direkten Feldern von M und globalen Variablen (im Kontext — DI-Provider). Alles andere ist eine LoD-Verletzung.
Das Gesetz entstand im Demeter-Projekt (Northeastern University, 1987), das sich auf die Codegenerierung auf der Grundlage formaler Spezifikationen konzentrierte. Die Forscher stellten fest, dass bei einer Änderung der Datenstruktur in der Spezifikation der Code an allen Stellen neu geschrieben werden musste, an denen die Aufrufkette durch den geänderten Typ verlief. LoD wurde zu einer formalen Regel, die dieses Problem verhindert.
Laut Karl Lieberherr: „The Art of Growing a System“ (2017) geben Projekte, die LoD systematisch über einen statischen Analysator prüfen, 22% weniger Zeit für Refactoring bei Änderungen von Datenmodellen aus. Die automatischen Korrekturen des Analysators für Aufrufketten schlagen die richtige Architektur vor. LoD ist keine Ästhetik, sondern eine messbare Reduzierung der Änderungskosten.
Integrieren Sie LoD-Prüfungen in Ihr CI über Detekt (Android, Regel „TooManyFunctions“ + benutzerdefiniert) oder SwiftLint (iOS, Erweiterung der Regel „nimble_operator“). Konfigurieren Sie es so, dass es bei Warnungen mit Ketten von mehr als 2 Aufrufen fehlschlägt.
Formal besagt LoD: Eine Methode f der Klasse C darf nur Methoden der folgenden Objekte aufrufen: this (C selbst), Argumente von f, innerhalb von f erstellte Objekte, direkte Felder von C und Rückgabewerte von Aufrufen vorheriger Schritte — mit der Einschränkung, dass die Kette nicht über einen Schritt hinausgeht. Einfach ausgedrückt: object.getX().getY().doZ() ist nach dem ersten getX() eine Verletzung.
Die formale Regel ist leicht zu automatisieren: Ein statischer Analysator prüft, ob Ausdrücke wie a.b().c().d() keine Ketten länger als 2 haben. Detekt (Android) und Tailor (iOS) unterstützen solche Prüfungen. Legen Sie den Schwellenwert fest: maximal 2 Punktaufrufe in einem einzelnen Ausdruck.
Aufrufketten (Train Wrecks) sind das Hauptsymptom von LoD-Verletzungen. Wenn Code a.getB().getC().getD().doSomething() schreibt, nimmt Objekt a Kenntnis von der Struktur nicht nur von b, sondern auch von c und d. Eine Änderung in einem beliebigen Glied der Kette bricht diesen Aufruf, obwohl a nur von b wissen sollte.
Betrachten Sie einen realen Fall: In einer iOS-App erhält ein Profilbildschirm user.address.city.name über eine Kette. Der Designer beschließt, city aus der Adresse zu entfernen. Nun müssen alle Stellen gefunden und korrigiert werden, die city.name verwenden — jede kann brechen. Wenn der Profilbildschirm user.displayAddress() anfordern würde, würde die Änderung nur User betreffen. LoD verhindert kaskadierende Korrekturen.
Eine Studie von Microsoft Research: „An Empirical Study of Law of Demeter in Practice“ (2021) analysierte 500 Open-Source-Projekte und stellte fest, dass jeder 10. Commit eine Korrektur einer durch eine Modelländerung zerbrochenen Aufrufkette enthält. Darüber hinaus befinden sich 68% dieser Korrekturen in Dateien, die nichts mit dem geänderten Modell zu tun haben. Ketten verbreiten Änderungen über die gesamte Codebasis.
Verwenden Sie LoD als Code-Review-Regel: Wenn Sie eine Kette von 3+ Aufrufen sehen, fordern Sie ein Refactoring. Die Ausnahme ist das Builder-Muster (Konstruktor), bei dem eine Kette LoD nicht verletzt, da jeder Aufruf denselben Builder zurückgibt.
Transitive Zugriffe sind das häufigste Beispiel für LoD-Verletzungen. Code erhält ein Objekt, dringt dann über Getter in dieses Objekt ein, dann in das nächste. Jeder Getter legt die interne Struktur offen und lädt zu LoD-Verletzungen ein.
// LoD-Verletzung: Kette von 4 Aufrufen
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Korrektur: Tell, Don’t Ask — lassen Sie Order es bereitstellen
class Order {
fun getUserCityName(): String =
user.address.city.name
}
In der ersten Version weiß OrderViewModel, dass Order einen User hat, User einen Address, Address einen City und City einen name. Wenn City name in title umbenennt, brechen alle Aufrufe. Die Korrektur fügt eine Methode getUserCityName() in Order hinzu: ViewModel kennt nur Order, Order verbirgt die interne Struktur.
iOS-Projekte verletzen häufig LoD bei der Arbeit mit Ansichtshierarchien. Code greift auf view.subviews.first?.subviews.last zu und ändert ein UILabel darin. Dies ist ein transitiver Zugriff auf die interne UI-Struktur, der bei der geringsten Änderung der Hierarchie bricht.
// LoD-Verletzung: Zugriff auf interne Ansichtshierarchie
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Neuer Text"
}
// Korrektur: Methode auf UIView, die Hierarchie verbirgt
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
Die UIView-Erweiterung verbirgt die Navigation durch Subviews. Externer Code erhält titleLabel direkt, ohne die interne Struktur zu kennen. Eine Änderung der Ansichtshierarchie betrifft nur die Erweiterung, nicht dutzende Stellen, an denen dieses UILabel verwendet wird.
Breites Interface (Getter für alle internen Felder) — die Hauptursache für LoD-Verletzungen. Wenn ein Objekt alle seine Interna offenlegt, beginnen Clients unweigerlich, diese transitiv zu durchlaufen. Die Lösung: Ersetzen Sie Getter durch Methoden, die sinnvolle Aktionen ausführen (Tell, Don’t Ask).
Statt user.address.city.name stellen Sie user.getCityName() bereit. Statt order.items.getTotal() stellen Sie order.getTotalPrice() bereit. Jede solche Methode kapselt eine Kette und schützt Clients vor Änderungen der internen Struktur. Laut Martin Fowler: „Refactoring, 2nd Edition“ (2019) ist das Ersetzen von transitivem Zugriff durch eine Vermittlermethode eine der vorteilhaftesten Refactorings in Bezug auf das Nutzen-Aufwand-Verhältnis.
Überprüfen Sie alle öffentlichen Getter, die veränderbare Objekte zurückgeben. Wenn ein Getter ein komplexes Objekt statt eines Primitivs zurückgibt, ist dies eine potenzielle LoD-Verletzung. Fügen Sie eine Methode hinzu, die die erforderliche Aktion ausführt, und schränken Sie den Zugriff auf den Getter ein.
Fassade (Facade) ist ein Architekturmuster, das eine einfache Schnittstelle zu einem komplexen Subsystem bereitstellt. Im Kontext von LoD ist eine Fassade eine Klasse, über die ein Client mit einer Gruppe von Objekten kommuniziert, ohne deren interne Struktur zu kennen. Repository in Android ist eine klassische Fassade, die Ketten von DataSource → API → Cache verbirgt.
// Fassade: Repository verbirgt Kette von Datenquellen
class PaymentRepository(
private val api: PaymentApi,
private val cache: PaymentCache,
private val analytics: AnalyticsTracker
) {
suspend fun processPayment(amount: Double): Result {
analytics.track("payment_start")
val result = api.charge(amount)
cache.save(result)
return result
}
}
// ViewModel weiß nichts von api, cache oder analytics
viewModel.processPayment(amount)
PaymentRepository ist eine Fassade: ViewModel ruft eine Methode auf, processPayment, und das Repository koordiniert API, Cache und Analysen intern. ViewModel hat keine Aufrufketten zu api.charge() oder cache.save() — das würde LoD verletzen. Die gesamte interne Struktur ist hinter einem einzigen Aufruf verborgen.
Übermäßige Wrapper — wenn ein Entwickler dutzende Vermittlermethoden erstellt, die einfach einen Aufruf von einer Klasse an eine andere delegieren. Order.getUserEmail() = user.email ist ein nutzloser Wrapper. LoD erfordert keine Wrapper für jedes Feld — es erfordert das Verbergen von Ketten, nicht von einzelnen einfachen Feldern.
Das Kriterium: Wenn ein Wrapper einfach ein Feld ohne Transformation und ohne Verbergen einer Kette zurückgibt, ist er nicht erforderlich. Order.getUserEmail() ist ein schlechter Wrapper, weil user.email ein direkter Zugriff auf ein Feld eines benachbarten Objekts ist und user ein direktes Feld von Order ist, was LoD erlaubt. Eine Verletzung wäre es, wenn Order user.getEmail() über zwei Schritte zurückgibt: zuerst user, dann email.
Erstellen Sie keine Wrapper für direkte Felder (der Zugriff auf ein Feld Ihres eigenen Objekts oder ein direktes Feld ist von LoD erlaubt). Erstellen Sie Wrapper, wenn ein Client beginnt, transitiv zu durchlaufen: a.b().c().d() → a.b().d() oder a.d().
LoD gilt für Verhalten, nicht für Daten. Datenklassen (DTOs — einfache Datencontainer) sind nicht verpflichtet, LoD zu befolgen: Ihr Zweck ist es, Daten offenzulegen. OrderDTO.items[0].price ist keine LoD-Verletzung, da ein DTO per Definition eine Datenstruktur ist, kein Objekt mit Verhalten. Die Verwechslung zwischen Objekten und Datenstrukturen ist einer der häufigsten Fehler.
Die Unterscheidung traf Robert C. Martin: „Clean Code“ (2008): „Objekte verbergen Daten und legen Verhalten offen. Datenstrukturen legen Daten offen und haben kein Verhalten.“ LoD gilt für Objekte mit Verhalten. Für Datenstrukturen (DTOs, JSON-Modelle) sind Zugriffsketten zulässig. Sobald eine Datenstruktur eine Methode mit Logik erhält, wird sie zu einem Objekt und muss LoD befolgen.
Unterscheiden Sie: Wenn eine Klasse nur Felder ohne Methoden enthält (DTO), gilt LoD nicht. Wenn eine Klasse Methoden mit Logik enthält, ist LoD obligatorisch. Überprüfen Sie im Code-Review: Ist dies eine Datenklasse (DTO) oder ein Objekt (mit Methoden)?
Häufig gestellte Fragen
Gesetz von Demeter (LoD): Ein Objekt kann nur mit engen Freunden kommunizieren — sich selbst, seinen Feldern, Parametern seiner Methoden und von ihm erstellten Objekten. Sie können keine Kette durchlaufen: a.getB().getC().doSomething() — das ist eine Verletzung.
LoD geht darum, MIT WELCHEN Objekten Sie interagieren können (nur unmittelbare Nachbarn). Tell, Don’t Ask geht darum, WIE Sie interagieren (fragen Sie nicht nach Daten, sagen Sie, es zu tun). Sie ergänzen sich: LoD schränkt den Kommunikationskreis ein, Tell Don’t Ask definiert die Art der Interaktion.
LoD kann für DTOs (Datenübertragungsobjekte) und einfache Datenstrukturen ohne Logik verletzt werden. Auch das Builder-Muster gilt nicht als Verletzung, da jeder Aufruf denselben Builder zurückgibt. Ausnahmen: Ketten in Stream API (map, filter) sind keine LoD-Verletzungen.
Detekt hat die Regel TooManyFunctions (indirekt), aber für die direkte Kettenprüfung verwenden Sie die Regel DataClassShouldBeImmutable und benutzerdefinierte Prüfungen über bindingReference. Konfigurieren Sie CI: Ketten mit mehr als 2 Aufrufen — Warnung, mehr als 3 — Build-Fehler.
SwiftLint hat keine eingebaute Regel für LoD, aber Sie können eine benutzerdefinierte Regel über Regex erstellen: Ketten wie \..+\.\..+\.\..+ (3+ Punktaufrufe). Alternative: Verwenden Sie die Regel nimble_operator und erweitern Sie sie, um lange Ketten zu erkennen.
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.