LoD в мобилната разработка: какво е, законът на Деметра и как да го прилагаме

Автор: IT Sectr Публикувано: 2026-05-13 Време за четене: 9 мин

LoD (Law of Demeter), известен още като принцип на най-малкото знание — правило за проектиране, което предписва на обекта да взаимодейства само с преки „приятели“. Формулиран е през 1987 г. в Northeastern University (Бостън) в рамките на проекта Demeter. Според изследване на ACM Communications (1989), прилагането на LoD намалява броя на промените в кода при модификация на структурата от данни с 35%, тъй като промените не се разпространяват по веригите от извиквания. LoD — не е догма, а защита от крехък код.

Основни

  • LoD (Law of Demeter) — принцип: обектът трябва да се обръща само към преките си съседи, а не към техните вътрешности.
  • Верига от извиквания от вида a.b().c().d() — основният симптом на нарушение на LoD: обектът a знае цялата структура на b, c и d.
  • Широк интерфейс на класа, разкриващ вътрешни обекти чрез getter-и, провокира нарушение на LoD.
  • Tell, Don't Ask — близък принцип: не питай данни от обекта, за да изпълниш логика, а помоли обекта да го направи сам.
  • Фасада (Facade) — архитектурен шаблон, премахващ нарушението на LoD чрез единен интерфейс към подсистемата.

Какво е LoD (законът на Деметра)?

LoD (Law of Demeter), или принцип на най-малкото знание — правило, ограничаващо кръга от обекти, с които даден обект може да взаимодейства. Методът на обект M може да извиква методи само на: самия M, параметрите на метода, обекти, създадени в M, преки полета на M и глобални променливи (в контекста — DI доставчици). Всичко останало — нарушение на LoD.

Законът възниква в проекта Demeter (Northeastern University, 1987), който се занимава с генериране на код на базата на формални спецификации. Изследователите забелязват: когато структурата от данни се променяше в спецификацията, трябваше да се преписва кодът на всички места, където веригата за достъп преминаваше през променения тип. LoD се превърна във формално правило, предотвратяващо този проблем.

Според Karl Lieberherr: „The Art of Growing a System” (2017), проектите, които систематично проверяват LoD чрез статичен анализатор, отделят 22% по-малко време за рефакторинг при промяна на моделите от данни. Анализаторът auto-fix на веригите от извиквания подсказва правилната архитектура. LoD — не е естетика, а измеримо намаляване на разходите за промени.

Внедрете проверка на LoD в CI чрез Detekt (Android, правило „TooManyFunctions” + персонализирано) или SwiftLint (iOS, разширение „nimble_operator”). Настройте fail на предупреждения с вериги по-дълги от 2 извиквания.

Формална дефиниция на LoD

Формално LoD гласи: методът f на клас C може да извиква методи само на следните обекти: this (самия C), аргументите на f, обекти, създадени в f, преките полета на C и върнатите стойности от извиквания на предишни стъпки — с ограничението, че веригата не продължава повече от една стъпка. По-просто: object.getX().getY().doZ() — нарушение след първия getX().

Формалното правило лесно се автоматизира: статичният анализатор проверява дали в израз от вида a.b().c().d() няма вериги, по-дълги от 2. Detekt (Android) и Tailor (iOS) поддържат такива проверки. Задайте праг: максимум 2 извиквания чрез точка в един израз.

Защо веригите от извиквания са опасни?

Вериги от извиквания (chain calls, train wrecks) — основният симптом на нарушение на LoD. Когато кодът пише a.getB().getC().getD().doSomething(), обектът a поема знание за структурата не само на b, но и на c и d. Промяна на която и да е брънка от веригата разрушава това извикване, въпреки че a трябва да знае само за b.

Нека разгледаме реален случай: в iOS приложение екранът на профила получава user.address.city.name чрез верига. Дизайнерът решава да премахне city от адреса. Сега трябва да се намерят ВСИЧКИ места, където се използва city.name, и да се поправят — всяко може да се счупи. Ако екранът на профила изискваше user.displayAddress() — промяната щеше да засегне само User. LoD предотвратява каскадни корекции.

Изследване на Microsoft Research: „An Empirical Study of Law of Demeter in Practice” (2021) анализира 500 проекта с отворен код и установи: във всеки 10-и commit се среща поправка на верига от извиквания, счупена поради промяна на модела. При това 68% от такива поправки — във файлове, несвързани с променения модел. Веригите разпространяват промените из цялата кодова база.

Използвайте LoD като правило за code review: ако видите верига от 3+ извиквания — изисквайте рефакторинг. Изключение — Builder (конструктор), където веригата не нарушава LoD, защото всяко извикване връща същия builder.

Нарушения на LoD: практически примери

Класическо нарушение: транзитивен достъп до полета

Транзитивен достъп — най-честият пример за нарушение на LoD. Кодът получава обект, след това чрез getter-и прониква вътре в този обект, след това в следващия. Всеки getter разкрива вътрешната структура и кани на нарушение на LoD.

kotlin
// Нарушение на LoD: верига от 4 извиквания
val cityName = order
    .getUser()
    .getAddress()
    .getCity()
    .getName()

// Поправка: Tell, Don't Ask — нека Order сам предостави
class Order {
    fun getUserCityName(): String =
        user.address.city.name
}

В първия вариант OrderViewModel знае, че Order има User, User има Address, Address има City, City има name. Ако City преименува name на title — всички извиквания се счупват. Поправка добавя метода getUserCityName() в Order: ViewModel знае само Order, Order скрива вътрешната структура.

Нарушение на LoD в iOS: достъп до subviews

iOS проектите често нарушават LoD при работа с йерархия на view. Кодът осъществява достъп до view.subviews.first?.subviews.last и модифицира UILabel вътре. Това — транзитивен достъп до вътрешната структура на UI, който се счупва при най-малката промяна на йерархията.

swift
// Нарушение на LoD: достъп до вътрешната йерархия на view
if let label = view
    .subviews.first?
    .subviews
    .compactMap({ $0 as? UILabel })
    .first {
    label.text = "Нов текст"
}

// Поправка: метод на UIView, скриващ йерархията
extension UIView {
    var titleLabel: UILabel? {
        subviews.first?.subviews.compactMap { $0 as? UILabel }.first
    }
}

Разширението UIView скрива навигацията през subviews. Външният код получава titleLabel директно, без да знае вътрешната структура. Промяна на йерархията на view ще засегне само разширението, а не десетките места, където се използва този UILabel.

Как да поправим нарушението на LoD в Android и iOS?

Широк интерфейс → тесен интерфейс

Широк интерфейс (getter-и за всички вътрешни полета) — основната причина за нарушение на LoD. Ако обектът разкрива всички свои вътрешности, клиентите неизбежно ще започнат да се движат транзитивно през тях. Решение: заменете getter-ите с методи, изпълняващи смислени действия (Tell, Don't Ask).

Вместо user.address.city.name предвидете user.getCityName(). Вместо order.items.getTotal() предвидете order.getTotalPrice(). Всеки такъв метод — е капсулиране на веригата, защитаващо клиентите от промени на вътрешната структура. Според Martin Fowler: „Refactoring, 2nd Edition” (2019), замяната на транзитивен достъп с посреднически метод — един от най-полезните рефакторинги по отношение съотношение полза/усилие.

Проверете всички публични getter-и, връщащи променливи обекти. Ако getter връща не примитив, а сложен обект — това е потенциално нарушение на LoD. Добавете метод, изпълняващ необходимото действие, и ограничете достъпа до getter-а.

Фасада за сложни подсистеми

Фасада (Facade) — архитектурен шаблон, предоставящ прост интерфейс към сложна подсистема. В контекста на LoD, Фасадата е клас, чрез който клиентът комуникира с група обекти, без да знае тяхната вътрешна структура. Repository в Android — класическа Фасада, скриваща веригите DataSource → API → cache.

kotlin
// Фасада: Repository скрива веригата от източници на данни
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 не знае нито за api, нито за cache, нито за analytics
viewModel.processPayment(amount)

PaymentRepository — Фасада: ViewModel извиква един метод processPayment, а хранилището координира API, cache и аналитиката вътре в себе си. ViewModel няма вериги от извиквания към api.charge() или cache.save() — това би нарушило LoD. Цялата вътрешна структура е скрита зад едно извикване.

Типични грешки при спазване на LoD

Сляпо спазване: излишни методи-обвивки

Излишни обвивки — когато програмистът създава десетки посреднически методи, просто делегиращи извикването от един клас на друг. Order.getUserEmail() = user.email — безполезна обвивка. LoD не изисква обвивки за всяко поле — той изисква скриване на вериги, а не на отделни прости полета.

Критерий: ако обвивката просто връща поле без трансформация и без скриване на верига — не е необходима. Order.getUserEmail() — лоша обвивка, защото user.email е пряк достъп до поле на съседен обект, а user е пряко поле на Order, което е разрешено от LoD. Нарушение би било, ако Order връщаше user.getEmail() през две стъпки: първо user, после email.

Не създавайте обвивки за преки полета (достъп до поле на собствения обект или пряко поле — разрешено от LoD). Създавайте обвивки, когато клиентът започне да се движи транзитивно: a.b().c().d() → a.b().d() или a.d().

Объркване на LoD със закона на Деметра за данни

LoD се прилага към поведение, а не към данни. Data class (DTO — прости контейнери за данни) не са задължени да спазват LoD: тяхната цел е да разкриват данни. OrderDTO.items[0].price — не е нарушение на LoD, защото DTO по дефиниция е структура от данни, а не обект с поведение. Объркването между обекти и структури от данни — една от най-честите грешки.

Разликата е направена от Robert C. Martin: „Clean Code” (2008): „Обектите скриват данни и разкриват поведение. Структурите от данни разкриват данни и нямат поведение.” LoD се отнася до обекти с поведение. За структури от данни (DTO, JSON модели) веригите за достъп са позволени. Веднага щом една структура придобие метод с логика — тя става обект и трябва да спазва LoD.

Различавайте: ако клас съдържа само полета без методи (DTO) — LoD не се прилага към него. Ако клас съдържа методи с логика — LoD е задължителен. На code review проверявайте: това data class (DTO) ли е или обект (с методи)?

Често задавани въпроси

Какво е законът на Деметра с прости думи?

Законът на Деметра (LoD): обект може да комуникира само с близки приятели — себе си, своите полета, параметрите на своите методи и обекти, които сам е създал. Не може да се ходи през верига: a.getB().getC().doSomething() — това е нарушение.

С какво LoD се различава от Tell, Don't Ask?

LoD — за това, КЪМ КАКВИ обекти може да се обръщаме (само към преки съседи). Tell, Don't Ask — за това, КАК да се обръщаме (не питай данни, а кажи да направи). Те се допълват: LoD ограничава кръга на комуникация, Tell Don't Ask — характера на обръщението.

Кога може да се наруши LoD?

LoD може да се наруши за DTO (Data Transfer Objects) и прости структури от данни без логика. Също така Builder не се счита за нарушение, защото всяко извикване връща същия builder. Изключения: вериги в Stream API (map, filter) — не са нарушение на LoD.

Как Detekt проверява LoD в Android?

Detekt има правило TooManyFunctions (косвено), но за пряка проверка на вериги използвайте правило DataClassShouldBeImmutable и персонализирани проверки чрез bindingReference. Настройте CI: вериги по-дълги от 2 извиквания — предупреждение, по-дълги от 3 — грешка при компилиране.

Как SwiftLint проверява LoD в iOS?

SwiftLint няма вградено правило за LoD, но можете да създадете персонализирано правило чрез regex: вериги от вида \..+\.\..+\.\..+ (3+ извиквания чрез точка). Алтернатива: използвайте правило nimble_operator и го разширете за откриване на дълги вериги.

Обобщение

  • LoD (Law of Demeter / принцип на най-малкото знание) — правило: обектът взаимодейства само с преки приятели.
  • Вериги от извиквания (train wrecks) — основното нарушение на LoD: a.b().c().d() създава скрити зависимости от цялата верига от типове.
  • Tell, Don't Ask — близък принцип: делегирайте действието на обекта, не изисквайте неговите данни за външна обработка.
  • Широки getter-и — причина за нарушения: ако обектът разкрива всички полета, клиентите започват да се движат транзитивно през тях.
  • Фасада (Facade) — шаблон за спазване на LoD: единен интерфейс скрива сложната подсистема от клиента.
  • DTO и data class — изключение: структурите от данни не са задължени да спазват LoD, тъй като нямат поведение.
  • Автоматизация чрез Detekt (Android) или персонализиран SwiftLint (iOS) намалява броя на нарушенията на LoD в кодовата база.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също