LoD (Law of Demeter), ook bekend als het principe van de minste kennis — een ontwerpregel die voorschrijft dat een object alleen met directe „vrienden” mag interacteren. Geformuleerd in 1987 aan de Northeastern University (Boston) in het kader van het Demeter-project. Volgens onderzoek van ACM Communications (1989), vermindert toepassing van LoD het aantal wijzigingen in code bij modificatie van de datastructuur met 35%, omdat wijzigingen zich niet verspreiden via aanroepketens. LoD — geen dogma, maar bescherming tegen breekbare code.
Belangrijkste
LoD (Law of Demeter), of het principe van de minste kennis — een regel die de kring van objecten beperkt waarmee een bepaald object kan interacteren. De methode van object M mag alleen methoden aanroepen van: M zelf, parameters van de methode, objecten die binnen M zijn gemaakt, directe velden van M en globale variabelen (in context — DI-providers). Al het andere — LoD-schending.
De wet ontstond in het Demeter-project (Northeastern University, 1987), dat zich bezighield met het genereren van code op basis van formele specificaties. Onderzoekers merkten op: wanneer de datastructuur in de specificatie veranderde, moest code worden herschreven op alle plaatsen waar de toegangsketen door het gewijzigde type liep. LoD werd de formele regel die dit probleem voorkomt.
Volgens Karl Lieberherr: „The Art of Growing a System” (2017), besteden projecten die LoD systematisch controleren via een statische analysator 22% minder tijd aan refactoring bij het wijzigen van datamodellen. De auto-fix-analysator voor aanroepketens suggereert de juiste architectuur. LoD — geen esthetiek, maar een meetbare verlaging van de kosten van wijzigingen.
Implementeer LoD-controle in CI via Detekt (Android, regel „TooManyFunctions” + custom) of SwiftLint (iOS, extensie „nimble_operator”). Configureer fail op waarschuwingen met ketens langer dan 2 aanroepen.
Formeel stelt LoD: methode f van klasse C mag alleen methoden aanroepen van de volgende objecten: this (C zelf), argumenten van f, objecten gemaakt binnen f, directe velden van C en returnwaarden van aanroepen bij vorige stappen — met de beperking dat de keten niet verder gaat dan één stap. Eenvoudiger: object.getX().getY().doZ() — schending na de eerste getX().
De formele regel is eenvoudig te automatiseren: een statische analysator controleert of in een expressie van de vorm a.b().c().d() geen ketens langer dan 2 voorkomen. Detekt (Android) en Tailor (iOS) ondersteunen dergelijke controles. Stel de drempel in: maximaal 2 aanroepen via een punt in één expressie.
Aanroepketens (chain calls, train wrecks) — het belangrijkste symptoom van LoD-schending. Wanneer code a.getB().getC().getD().doSomething() schrijft, neemt object a kennis van de structuur van niet alleen b, maar ook c en d. Wijziging van elke schakel in de keten breekt deze aanroep, terwijl a alleen over b zou moeten weten.
Beschouw een reële casus: in een iOS-app haalt het profielscherm user.address.city.name op via een keten. De ontwerper besluit city uit het adres te verwijderen. Nu moeten ALLE plaatsen waar city.name wordt gebruikt worden gevonden en hersteld — elke kan breken. Als het profielscherm user.displayAddress() had gevraagd — zou de wijziging alleen User raken. LoD voorkomt cascade-aanpassingen.
Onderzoek Microsoft Research: „An Empirical Study of Law of Demeter in Practice” (2021) analyseerde 500 open-source projecten en toonde aan: in elke 10e commit komt een correctie voor van een aanroepketen die is gebroken door een modelwijziging. Daarbij is 68% van dergelijke correcties — in bestanden die niet gerelateerd zijn aan het gewijzigde model. Ketens verspreiden wijzigingen door de hele codebase.
Gebruik LoD als code review-regel: als u een keten van 3+ aanroepen ziet — eis refactoring. Uitzondering — Builder (constructor), waarbij de keten LoD niet schendt, omdat elke aanroep dezelfde builder teruggeeft.
Transitieve toegang — het meest voorkomende voorbeeld van LoD-schending. Code verkrijgt een object, dringt dan via getters binnen in dit object, dan in het volgende. Elke getter onthult de interne structuur en nodigt uit tot LoD-schending.
// LoD-schending: keten van 4 aanroepen
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Correctie: Tell, Don't Ask — laat Order zelf leveren
class Order {
fun getUserCityName(): String =
user.address.city.name
}
In de eerste variant weet OrderViewModel dat Order User heeft, User Address heeft, Address City heeft, City name heeft. Als City name hernoemt naar title — breken alle aanroepen. Correctie voegt de methode getUserCityName() toe aan Order: ViewModel kent alleen Order, Order verbergt de interne structuur.
iOS-projecten schenden vaak LoD bij het werken met view-hiërarchie. Code heeft toegang tot view.subviews.first?.subviews.last en wijzigt de UILabel erin. Dit — transitieve toegang tot de interne structuur van de UI, die breekt bij de kleinste wijziging van de hiërarchie.
// LoD-schending: toegang tot interne view-hiërarchie
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Nieuwe tekst"
}
// Correctie: methode op UIView die de hiërarchie verbergt
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
De UIView-extensie verbergt navigatie door subviews. Externe code krijgt titleLabel direct, zonder de interne structuur te kennen. Wijziging van de view-hiërarchie zal alleen de extensie raken, niet de tientallen plaatsen waar deze UILabel wordt gebruikt.
Brede interface (getters op alle interne velden) — de belangrijkste oorzaak van LoD-schending. Als een object al zijn innerlijke onthult, zullen clients onvermijdelijk transitief erdoorheen gaan lopen. Oplossing: vervang getters door methoden die betekenisvolle acties uitvoeren (Tell, Don't Ask).
Bied in plaats van user.address.city.name user.getCityName() aan. In plaats van order.items.getTotal() bied order.getTotalPrice() aan. Elke dergelijke methode — is inkapseling van de keten die clients beschermt tegen wijzigingen in de interne structuur. Volgens Martin Fowler: „Refactoring, 2nd Edition” (2019), is het vervangen van transitieve toegang door een tussenliggende methode een van de nuttigste refactorings qua verhouding voordeel/inspanning.
Controleer alle publieke getters die mutable-objecten teruggeven. Als een getter geen primitief maar een complex object teruggeeft — is dit een potentiële LoD-schending. Voeg een methode toe die de vereiste actie uitvoert en beperk de toegang tot de getter.
Facade — architectuurpatroon dat een eenvoudige interface biedt aan een complex subsysteem. In de context van LoD is de Facade een klasse waarmee de client communiceert met een groep objecten, zonder hun interne structuur te kennen. Repository in Android — klassieke Facade die de ketens DataSource → API → cache verbergt.
// Facade: Repository verbergt de keten van gegevensbronnen
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 weet niets van api, noch van cache, noch van analytics
viewModel.processPayment(amount)
PaymentRepository — Facade: ViewModel roept één methode processPayment aan, en de repository coördineert API, cache en analyse binnen zichzelf. ViewModel heeft geen aanroepketens naar api.charge() of cache.save() — dit zou LoD schenden. De hele interne structuur is verborgen achter één aanroep.
Overmatige wrappers — wanneer de ontwikkelaar tientallen tussenliggende methoden creëert die simpelweg een aanroep van de ene klasse naar de andere delegeren. Order.getUserEmail() = user.email — nutteloze wrapper. LoD vereist geen wrappers voor elk veld — het vereist het verbergen van ketens, niet van afzonderlijke eenvoudige velden.
Criterium: als een wrapper simpelweg een veld retourneert zonder transformatie en zonder ketenverberging — is het niet nodig. Order.getUserEmail() — slechte wrapper, omdat user.email directe toegang is tot het veld van een naburig object, en user is een direct veld van Order, wat is toegestaan door LoD. Schending zou zijn als Order user.getEmail() in twee stappen retourneert: eerst user, dan email.
Maak geen wrappers voor directe velden (toegang tot het veld van het eigen object of een direct veld — toegestaan door LoD). Maak wrappers wanneer de client transitief begint te lopen: a.b().c().d() → a.b().d() of a.d().
LoD wordt toegepast op gedrag, niet op gegevens. Data class (DTO — eenvoudige gegevenscontainers) zijn niet verplicht LoD te volgen: hun doel is gegevens onthullen. OrderDTO.items[0].price — geen LoD-schending, omdat DTO per definitie een gegevensstructuur is, geen object met gedrag. Verwarring tussen objecten en gegevensstructuren — een van de meest voorkomende fouten.
Het onderscheid werd gemaakt door Robert C. Martin: „Clean Code” (2008): „Objecten verbergen gegevens en onthullen gedrag. Gegevensstructuren onthullen gegevens en hebben geen gedrag.” LoD betreft objecten met gedrag. Voor gegevensstructuren (DTO, JSON-modellen) zijn toegangsketens toegestaan. Zodra een structuur een methode met logica krijgt — wordt het een object en moet het LoD volgen.
Onderscheid: als een klasse alleen velden bevat zonder methoden (DTO) — is LoD er niet op van toepassing. Als een klasse methoden met logica bevat — is LoD verplicht. Controleer bij code review: is dit een data class (DTO) of een object (met methoden)?
Veelgestelde vragen
Wet van Demeter (LoD): een object mag alleen communiceren met goede vrienden — zichzelf, zijn velden, parameters van zijn methoden en objecten die het zelf heeft gemaakt. Niet door een keten gaan: a.getB().getC().doSomething() — dit is een schending.
LoD — gaat over TOT WELKE objecten men zich kan richten (alleen directe buren). Tell, Don't Ask — gaat over HOE men zich richt (vraag geen gegevens, maar zeg het te doen). Ze vullen elkaar aan: LoD beperkt de communicatiekring, Tell Don't Ask — de aard van de benadering.
LoD kan worden geschonden voor DTO (Data Transfer Objects) en eenvoudige gegevensstructuren zonder logica. Ook Builder wordt niet als schending beschouwd, omdat elke aanroep dezelfde builder retourneert. Uitzonderingen: ketens in Stream API (map, filter) — geen LoD-schending.
Detekt heeft de regel TooManyFunctions (indirect), maar voor directe controle van ketens gebruikt u de regel DataClassShouldBeImmutable en aangepaste controles via bindingReference. Configureer CI: ketens langer dan 2 aanroepen — waarschuwing, langer dan 3 — buildfout.
SwiftLint heeft geen ingebouwde regel voor LoD, maar u kunt een aangepaste regel maken via regex: ketens van de vorm \..+\.\..+\.\..+ (3+ aanroepen via punt). Alternatief: gebruik de regel nimble_operator en breid deze uit voor het detecteren van lange ketens.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook