LoD в мобильной разработке: что это, закон Деметры и как его применять

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

LoD (Law of Demeter), также известный как принцип наименьшего знания — правило проектирования, предписывающее объекту взаимодействовать только с непосредственными «друзьями». Сформулирован в 1987 году в Университете Нортистерн (Бостон) в рамках проекта Demeter. По данным исследования ACM Communications (1989), применение LoD снижает количество изменений в коде при модификации структуры данных на 35%, поскольку изменения не распространяются по цепочкам вызовов. LoD — не догма, а защита от хрупкого кода.

Главное

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

Что такое LoD (закон Деметры)?

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

Закон возник в проекте Demeter (Университет Нортистерн, 1987), который занимался генерацией кода на основе формальных спецификаций. Исследователи заметили: когда в спецификации менялась структура данных, приходилось переписывать код во всех местах, где цепочка обращений проходила через изменившийся тип. LoD стал формальным правилом, предотвращающим эту проблему.

По данным Karl Lieberherr: «The Art of Growing a System» (2017), проекты, систематически проверяющие LoD через статический анализатор, тратят на 22% меньше времени на рефакторинг при изменении моделей данных. Анализатор auto-fixes цепочек вызовов подсказывает правильную архитектуру. LoD — не эстетика, а измеримое снижение стоимости изменений.

Внедрите проверку LoD в CI через Detekt (Android, rule «TooManyFunctions» + custom) или SwiftLint (iOS, rule «nimble_operator» extension). Настройте fail на warnings с цепочками длиннее 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 open-source проектов и выявило: в каждом 10-м коммите встречается исправление цепочки вызовов, сломанной из-за изменения модели. При этом 68% таких исправлений — в файлах, не связанных с изменённой моделью. Цепочки распространяют изменения по всей кодовой базе.

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

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

Классическое нарушение: транзитивный доступ к полям

Транзитивный доступ — самый частый пример нарушения LoD. Код получает объект, затем через геттеры проникает внутрь этого объекта, затем внутрь следующего. Каждый геттер раскрывает внутреннюю структуру и приглашает к нарушению 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?

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

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

Вместо user.address.city.name предоставьте user.getCityName(). Вместо order.items.getTotal() предоставьте order.getTotalPrice(). Каждый такой метод — это инкапсуляция цепочки, защищающая клиентов от изменений внутренней структуры. По данным Martin Fowler: "Refactoring, 2nd Edition" (2019), замена транзитивного доступа на метод-посредник — один из самых полезных рефакторингов по соотношению выгода/усилие.

Проверьте все публичные геттеры, возвращающие mutable-объекты. Если геттер возвращает не примитив, а сложный объект — это потенциальное нарушение LoD. Добавьте метод, выполняющий нужное действие, и ограничьте доступ к геттеру.

Фасад для сложных подсистем

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

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, кэш и аналитику внутри себя. 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 с Law of Demeter для данных

LoD применяется к поведению, а не к данным. Data class (DTOs — простые контейнеры данных) не обязаны соблюдать 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 — близкий принцип: делегируйте действие объекту, а не запрашивайте его данные для внешней обработки.
  • Широкие геттеры — причина нарушений: если объект раскрывает все поля, клиенты начинают по ним транзитивно ходить.
  • Фасад (Facade) — паттерн для соблюдения LoD: единый интерфейс скрывает сложную подсистему от клиента.
  • DTO и data class — исключение: структуры данных не обязаны соблюдать LoD, так как не имеют поведения.
  • Автоматизация через Detekt (Android) или кастомный SwiftLint (iOS) снижает количество нарушений LoD в кодовой базе.

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

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также