LoD (Law of Demeter), también conocido como el principio de mínimo conocimiento, es una regla de diseño que prescribe que un objeto interactúe solo con sus “amigos” inmediatos. Fue formulado en 1987 en la Universidad Northeastern (Boston) como parte del proyecto Demeter. Según una investigación ACM Communications (1989), aplicar LoD reduce la cantidad de cambios en el código al modificar una estructura de datos en un 35%, porque los cambios no se propagan a través de cadenas de llamadas. LoD no es un dogma, sino una protección contra el código frágil.
Puntos clave
LoD (Law of Demeter), o el principio de mínimo conocimiento, es una regla que limita el conjunto de objetos con los que un objeto determinado puede interactuar. Un método del objeto M solo puede llamar a métodos de: el propio M, los parámetros del método, los objetos creados dentro de M, los campos directos de M y las variables globales (en contexto — proveedores de DI). Todo lo demás es una violación de LoD.
La ley se originó en el proyecto Demeter (Universidad Northeastern, 1987), que se centraba en la generación de código basada en especificaciones formales. Los investigadores notaron que cuando una estructura de datos cambiaba en la especificación, el código debía reescribirse en todos los lugares donde la cadena de llamadas pasaba por el tipo modificado. LoD se convirtió en una regla formal que prevenía este problema.
Según Karl Lieberherr: “The Art of Growing a System” (2017), los proyectos que verifican sistemáticamente LoD mediante un analizador estático gastan un 22% menos de tiempo en refactorización al cambiar modelos de datos. Las autocorrecciones del analizador para cadenas de llamadas sugieren la arquitectura correcta. LoD no es estética, sino una reducción medible del costo de los cambios.
Integre verificaciones de LoD en su CI mediante Detekt (Android, regla “TooManyFunctions” + personalizada) o SwiftLint (iOS, extensión de la regla “nimble_operator”). Configúrelo para fallar en advertencias con cadenas de más de 2 llamadas.
Formalmente, LoD establece: un método f de la clase C solo puede llamar a métodos de los siguientes objetos: this (el propio C), argumentos de f, objetos creados dentro de f, campos directos de C y valores de retorno de llamadas de pasos anteriores — con la restricción de que la cadena no continúe más allá de un paso. En términos simples: object.getX().getY().doZ() es una violación después del primer getX().
La regla formal es fácil de automatizar: un analizador estático verifica que expresiones como a.b().c().d() no tengan cadenas de más de 2. Detekt (Android) y Tailor (iOS) admiten tales verificaciones. Establezca el umbral: un máximo de 2 llamadas con punto en una sola expresión.
Las cadenas de llamadas (train wrecks) son el síntoma principal de las violaciones de LoD. Cuando el código escribe a.getB().getC().getD().doSomething(), el objeto a asume conocimiento de la estructura no solo de b, sino también de c y d. Un cambio en cualquier eslabón de la cadena rompe esta llamada, aunque a solo debería conocer a b.
Considere un caso real: en una app de iOS, una pantalla de perfil obtiene user.address.city.name a través de una cadena. El diseñador decide eliminar city de la dirección. Ahora hay que encontrar y corregir TODOS los lugares donde se usa city.name — cada uno puede romperse. Si la pantalla de perfil solicitara user.displayAddress(), el cambio solo afectaría a User. LoD previene correcciones en cascada.
Un estudio de Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) analizó 500 proyectos de código abierto y encontró que cada 10.º commit contiene una corrección de una cadena de llamadas rota por un cambio de modelo. Además, el 68% de dichas correcciones están en archivos no relacionados con el modelo modificado. Las cadenas propagan cambios por toda la base de código.
Use LoD como regla de code review: si ve una cadena de 3+ llamadas, exija una refactorización. La excepción es el patrón Builder (constructor), donde una cadena no viola LoD porque cada llamada devuelve el mismo builder.
El acceso transitivo es el ejemplo más común de violación de LoD. El código obtiene un objeto, luego a través de getters penetra dentro de ese objeto, luego dentro del siguiente. Cada getter expone la estructura interna e invita a violaciones de LoD.
// Violación de LoD: cadena de 4 llamadas
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Corrección: Tell, Don’t Ask — que Order lo proporcione
class Order {
fun getUserCityName(): String =
user.address.city.name
}
En la primera versión, OrderViewModel sabe que Order tiene un User, User tiene un Address, Address tiene un City y City tiene un name. Si City renombra name a title, todas las llamadas se rompen. La corrección añade un método getUserCityName() a Order: ViewModel solo conoce Order, Order oculta la estructura interna.
Los proyectos de iOS a menudo violan LoD al trabajar con jerarquías de vistas. El código accede a view.subviews.first?.subviews.last y modifica un UILabel interno. Esto es acceso transitivo a la estructura interna de la UI, que se rompe ante el más mínimo cambio en la jerarquía.
// Violación de LoD: acceso a la jerarquía interna de vistas
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Texto nuevo"
}
// Corrección: método en UIView que oculta la jerarquía
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
La extensión de UIView oculta la navegación a través de subviews. El código externo obtiene titleLabel directamente sin conocer la estructura interna. Un cambio en la jerarquía de vistas solo afectará a la extensión, no a decenas de lugares donde se usa este UILabel.
La interfaz amplia (getters para todos los campos internos) — la causa principal de violaciones de LoD. Si un objeto expone todos sus internos, los clientes inevitablemente comenzarán a recorrerlos transitivamente. La solución: reemplace los getters por métodos que realicen acciones significativas (Tell, Don’t Ask).
En lugar de user.address.city.name, proporcione user.getCityName(). En lugar de order.items.getTotal(), proporcione order.getTotalPrice(). Cada uno de esos métodos encapsula una cadena, protegiendo a los clientes de cambios en la estructura interna. Según Martin Fowler: “Refactoring, 2nd Edition” (2019), reemplazar el acceso transitivo por un método mediador es una de las refactorizaciones más beneficiosas en términos de relación beneficio/esfuerzo.
Revise todos los getters públicos que devuelven objetos mutables. Si un getter devuelve un objeto complejo en lugar de un primitivo, es una posible violación de LoD. Añada un método que realice la acción requerida y restrinja el acceso al getter.
Facade es un patrón arquitectónico que proporciona una interfaz simple a un subsistema complejo. En el contexto de LoD, un Facade es una clase a través de la cual un cliente se comunica con un grupo de objetos sin conocer su estructura interna. Repository en Android es un Facade clásico, que oculta cadenas de DataSource → API → caché.
// Facade: Repository oculta la cadena de fuentes de datos
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 no sabe nada sobre api, cache ni analytics
viewModel.processPayment(amount)
PaymentRepository es un Facade: ViewModel llama a un método, processPayment, y el repositorio coordina API, caché y analíticas internamente. ViewModel no tiene cadenas de llamadas a api.charge() o cache.save() — eso violaría LoD. Toda la estructura interna está oculta detrás de una sola llamada.
Envoltorios excesivos — cuando un desarrollador crea decenas de métodos mediadores que simplemente delegan una llamada de una clase a otra. Order.getUserEmail() = user.email es un envoltorio inútil. LoD no exige envoltorios para cada campo — exige ocultar cadenas, no campos individuales simples.
El criterio: si un envoltorio simplemente devuelve un campo sin transformación y sin ocultar una cadena, no es necesario. Order.getUserEmail() es un mal envoltorio porque user.email es acceso directo a un campo de un objeto vecino, y user es un campo directo de Order, lo que LoD permite. Una violación sería si Order devolviera user.getEmail() a través de dos pasos: primero user, luego email.
No cree envoltorios para campos directos (acceder a un campo de su propio objeto o a un campo directo está permitido por LoD). Cree envoltorios cuando un cliente comience a recorrer transitivamente: a.b().c().d() → a.b().d() o a.d().
LoD se aplica al comportamiento, no a los datos. Las clases de datos (DTO — contenedores simples de datos) no están obligadas a seguir LoD: su propósito es exponer datos. OrderDTO.items[0].price no es una violación de LoD porque un DTO es por definición una estructura de datos, no un objeto con comportamiento. La confusión entre objetos y estructuras de datos es uno de los errores más comunes.
La distinción la hizo Robert C. Martin: “Clean Code” (2008): “Los objetos ocultan datos y exponen comportamiento. Las estructuras de datos exponen datos y no tienen comportamiento.” LoD se aplica a objetos con comportamiento. Para estructuras de datos (DTO, modelos JSON), las cadenas de acceso son permisibles. Tan pronto como una estructura de datos obtiene un método con lógica, se convierte en un objeto y debe cumplir LoD.
Distinga: si una clase contiene solo campos sin métodos (DTO), LoD no se aplica. Si una clase contiene métodos con lógica, LoD es obligatorio. En el code review, verifique: ¿es una clase de datos (DTO) o un objeto (con métodos)?
Preguntas frecuentes
Ley de Demeter (LoD): un objeto solo puede comunicarse con amigos cercanos — consigo mismo, sus campos, los parámetros de sus métodos y los objetos que crea. No se puede recorrer una cadena: a.getB().getC().doSomething() — eso es una violación.
LoD trata sobre CON QUÉ objetos puedes interactuar (solo vecinos inmediatos). Tell, Don’t Ask trata sobre CÓMO interactuar (no pidas datos, ordena hacer). Se complementan: LoD limita el círculo de comunicación, Tell Don’t Ask define la naturaleza de la interacción.
LoD se puede violar para DTO (Objetos de Transferencia de Datos) y estructuras de datos simples que no contengan lógica. Además, el patrón Builder no se considera una violación porque cada llamada devuelve el mismo builder. Excepciones: las cadenas en Stream API (map, filter) no son violaciones de LoD.
Detekt tiene la regla TooManyFunctions (indirectamente), pero para la verificación directa de cadenas, use la regla DataClassShouldBeImmutable y verificaciones personalizadas a través de bindingReference. Configure CI: cadenas de más de 2 llamadas — advertencia, más de 3 — error de compilación.
SwiftLint no tiene una regla incorporada para LoD, pero puede crear una regla personalizada mediante regex: cadenas como \..+\.\..+\.\..+ (3+ llamadas con punto). Alternativa: use la regla nimble_operator y extiéndala para detectar cadenas largas.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también