LoD (Law of Demeter), shuningdek eng kam bilim printsipi sifatida tanilgan — ob'ektga faqat bevosita “do'stlar” bilan o'zaro aloqa qilishni buyuradigan loyihalash qoidasi. 1987-yilda Northeastern Universitetida (Boston) Demeter loyihasi doirasida shakllantirilgan. ACM Communications (1989) tadqiqotiga ko'ra, LoD qo'llanilishi ma'lumotlar tuzilmasini o'zgartirishda koddagi o'zgarishlar sonini 35% ga kamaytiradi, chunki o'zgarishlar chaqiruv zanjirlari bo'ylab tarqalmaydi. LoD — dogma emas, balki mo'rt koddan himoyadir.
Asosiy
LoD (Law of Demeter) yoki eng kam bilim printsipi — ma'lum bir ob'ekt o'zaro aloqa qilishi mumkin bo'lgan ob'ektlar doirasini cheklaydigan qoida. M ob'ektining metodi faqat quyidagilarning metodlarini chaqirishi mumkin: M ning o'zi, metod parametrlari, M ichida yaratilgan ob'ektlar, M ning bevosita maydonlari va global o'zgaruvchilar (kontekstda — DI provayderlari). Qolgan hamma narsa — LoD buzilishi.
Qonun Demeter loyihasida (Northeastern Universiteti, 1987) paydo bo'lgan, u rasmiy spetsifikatsiyalar asosida kod yaratish bilan shug'ullangan. Tadqiqotchilar payqashdi: spetsifikatsiyada ma'lumotlar tuzilishi o'zgarganda, zanjir o'zgartirilgan turdan o'tgan barcha joylarda kodni qayta yozish kerak edi. LoD bu muammoning oldini oladigan rasmiy qoidaga aylandi.
Karl Lieberherr: “The Art of Growing a System” (2017) ga ko'ra, statik analizator orqali LoD-ni muntazam tekshiradigan loyihalar ma'lumotlar modellarini o'zgartirishda refaktoringga 22% kam vaqt sarflaydi. Chaqiruv zanjirlarining auto-fix analizatori to'g'ri arxitekturani taklif qiladi. LoD — estetika emas, balki o'zgarishlar narxining o'lchanadigan pasayishidir.
CI-da LoD tekshiruvini Detekt (Android, “TooManyFunctions” qoidasi + custom) yoki SwiftLint (iOS, “nimble_operator” extension) orqali joriy qiling. 2 chaqiruvdan uzun zanjirlar uchun fail-ga warning qo'ying.
Rasmiy ravishda LoD aytadi: C sinfining f metodi faqat quyidagi ob'ektlarning metodlarini chaqirishi mumkin: this (C ning o'zi), f ning argumentlari, f ichida yaratilgan ob'ektlar, C ning bevosita maydonlari va oldingi qadamlardagi chaqiruvlar qaytargan qiymatlar — zanjir bir qadamdan ortiq davom etmasligi sharti bilan. Oddiyroq: object.getX().getY().doZ() — birinchi getX() dan keyin buzilish.
Rasmiy qoidani avtomatlashtirish oson: statik analizator a.b().c().d() ifodasida 2 dan uzun zanjirlar yo'qligini tekshiradi. Detekt (Android) va Tailor (iOS) bunday tekshiruvlarni qo'llab-quvvatlaydi. Chegarani belgilang: bitta ifodada nuqta orqali maksimal 2 chaqiruv.
Chaqiruv zanjirlari (chain calls, train wrecks) — LoD buzilishining asosiy alomati. Kod a.getB().getC().getD().doSomething() yozganda, a ob'ekti nafaqat b, balki c va d ning tuzilishi haqida ham bilim oladi. O'zgarish zanjirning istalgan halqasini bu chaqiruvni buzadi, holbuki a faqat b haqida bilishi kerak.
Haqiqiy holatni ko'rib chiqaylik: iOS ilovasida profil ekrani user.address.city.name ni zanjir orqali oladi. Dizayner manzildan city ni olib tashlashga qaror qiladi. Endi city.name ishlatiladigan BARCHA joylarni topish va tuzatish kerak — har biri sinishi mumkin. Agar profil ekrani user.displayAddress() ni so'raganida edi — o'zgarish faqat User ga ta'sir qilgan bo'lardi. LoD kaskadli tuzatishlarning oldini oladi.
Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) tadqiqoti 500 ochiq manbali loyihani tahlil qildi va har 10-chi commitda modelning o'zgarishi sababli buzilgan chaqiruv zanjirining tuzatilishi uchraydiganligini aniqladi. Bunda bunday tuzatishlarning 68% i o'zgartirilgan model bilan bog'liq bo'lmagan fayllarda. Zanjirlar o'zgarishlarni butun kod bazasi bo'ylab tarqatadi.
LoD ni code review qoidasi sifatida ishlating: agar 3+ chaqiruvdan iborat zanjir ko'rsangiz — refaktoring talab qiling. Istisno — Builder (konstruktor), bu erda zanjir LoD ni buzmaydi, chunki har bir chaqiruv bir xil builder ni qaytaradi.
Tranzit kirish — LoD buzilishining eng keng tarqalgan namunasi. Kod ob'ektni oladi, so'ngra getterlar orqali bu ob'ektning ichiga, so'ngra keyingi ob'ektning ichiga kiradi. Har bir getter ichki tuzilmani ochadi va LoD buzilishiga taklif qiladi.
// LoD buzilishi: 4 chaqiruvdan iborat zanjir
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Tuzatish: Tell, Don't Ask — Order o'zi ta'minlasin
class Order {
fun getUserCityName(): String =
user.address.city.name
}
Birinchi variantda OrderViewModel biladi: Order da User bor, User da Address bor, Address da City bor, City da name bor. Agar City name ni title ga o'zgartirsa — barcha chaqiruvlar buziladi. Tuzatish Order ga getUserCityName() metodini qo'shadi: ViewModel faqat Order ni biladi, Order ichki tuzilmani yashiradi.
iOS loyihalari ko'pincha view ierarxiyasi bilan ishlashda LoD ni buzadi. Kod view.subviews.first?.subviews.last ga murojaat qiladi va ichidagi UILabel ni o'zgartiradi. Bu — UI ning ichki tuzilmasiga tranzit kirish bo'lib, ierarxiyaning eng kichik o'zgarishida buziladi.
// LoD buzilishi: view ichki ierarxiyasiga kirish
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Yangi matn"
}
// Tuzatish: UIView da ierarxiyani yashiruvchi metod
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
UIView kengaytmasi subviews bo'ylab navigatsiyani yashiradi. Tashqi kod titleLabel ni to'g'ridan-to'g'ri oladi, ichki tuzilmani bilmagan holda. O'zgarish view ierarxiyasi faqat kengaytmaga ta'sir qiladi, bu UILabel ishlatiladigan o'nlab joylarga emas.
Keng interfeys (barcha ichki maydonlarga getterlar) — LoD buzilishining asosiy sababi. Agar ob'ekt barcha ichki qismlarini ochib qo'ysa, mijozlar muqarrar ravishda ular bo'ylab tranzit yura boshlaydilar. Yechim: getterlarni mazmunli harakatlarni bajaradigan metodlar bilan almashtiring (Tell, Don't Ask).
user.address.city.name o'rniga user.getCityName() ni taqdim eting. order.items.getTotal() o'rniga order.getTotalPrice() ni taqdim eting. Har bir bunday metod — mijozlarni ichki tuzilma o'zgarishlaridan himoya qiluvchi zanjir inkapsulatsiyasidir. Martin Fowler: “Refactoring, 2nd Edition” (2019) ga ko'ra, tranzit kirishni vositachi metod bilan almashtirish foyda/urinish nisbati bo'yicha eng foydali refaktoringlardan biridir.
Mutable ob'ektlarni qaytaradigan barcha public getterlarni tekshiring. Agar getter primitiv emas, balki murakkab ob'ekt qaytarsa — bu potensial LoD buzilishi. Kerakli harakatni bajaradigan metod qo'shing va getter ga kirishni cheklang.
Fasad (Facade) — murakkab quyi tizimga oddiy interfeys taqdim etadigan arxitektura namunasi. LoD kontekstida Fasad — mijoz ichki tuzilmani bilmagan holda bir guruh ob'ektlar bilan aloqa qiladigan sinfdir. Repository Android da — DataSource → API → cache zanjirini yashiradigan klassik Fasad.
// Fasad: Repository ma'lumot manbalari zanjirini yashiradi
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 na api, na cache, na analytics haqida bilmaydi
viewModel.processPayment(amount)
PaymentRepository — Fasad: ViewModel bitta processPayment metodini chaqiradi, repository esa API, cache va analitikani o'z ichida muvofiqlashtiradi. ViewModel ning api.charge() yoki cache.save() ga chaqiruv zanjirlari yo'q — bu LoD ni buzgan bo'lardi. Butun ichki tuzilma bitta chaqiruv orqasida yashiringan.
Haddan tashqari o'rashlar — dasturchi bir sinfdan ikkinchisiga chaqiruvni oddiygina uzatadigan o'nlab vositachi metodlar yaratganda. Order.getUserEmail() = user.email — foydasiz o'rash. LoD har bir maydon uchun o'rash talab qilmaydi — u zanjirlarni yashirishni talab qiladi, alohida oddiy maydonlarni emas.
Mezon: agar o'rash hech qanday transformatsiyasiz va zanjirni yashirmasdan oddiygina maydonni qaytarsa — kerak emas. Order.getUserEmail() — yomon o'rash, chunki user.email qo'shni ob'ektning maydoniga to'g'ridan-to'g'ri kirishdir va user Order ning bevosita maydoni bo'lib, bu LoD tomonidan ruxsat etiladi. Buzilish Order user.getEmail() ni ikki qadamda qaytarganda bo'lar edi: avval user, keyin email.
To'g'ridan-to'g'ri maydonlar uchun o'rash yaratmang (o'z ob'ektingizning maydoniga yoki bevosita maydonga kirish — LoD tomonidan ruxsat etiladi). Mijoz tranzit yura boshlaganda o'rash yarating: a.b().c().d() → a.b().d() yoki a.d().
LoD xatti-harakatga nisbatan qo'llaniladi, ma'lumotlarga emas. Data class (DTO — oddiy ma'lumot konteynerlari) LoD ga rioya qilishlari shart emas: ularning maqsadi ma'lumotlarni ochishdir. OrderDTO.items[0].price — LoD buzilishi emas, chunki DTO ta'rifiga ko'ra ma'lumot tuzilmasidir, xatti-harakatga ega ob'ekt emas. Aralashtirish ob'ektlar va ma'lumot tuzilmalari o'rtasida — eng keng tarqalgan xatolardan biridir.
Farqni Robert C. Martin: “Clean Code” (2008) ko'rsatgan: “Ob'ektlar ma'lumotlarni yashiradi va xatti-harakatni ochadi. Ma'lumot tuzilmalari ma'lumotlarni ochadi va xatti-harakatga ega emas.” LoD xatti-harakatga ega ob'ektlarga tegishli. Ma'lumot tuzilmalari (DTO, JSON modellari) uchun kirish zanjirlari ruxsat etiladi. Tuzilmada mantiqli metod paydo bo'lishi bilanoq — u ob'ektga aylanadi va LoD ga rioya qilishi kerak.
Farqlang: agar sinf faqat maydonlardan iborat bo'lsa, metodlarsiz (DTO) — LoD unga nisbatan qo'llanilmaydi. Agar sinf mantiqli metodlarni o'z ichiga olsa — LoD majburiydir. Code review da tekshiring: bu data class (DTO) mi yoki ob'ekt (metodli)?
Tez-tez so'raladigan savollar
Demeter qonuni (LoD): ob'ekt faqat yaqin do'stlari bilan muloqot qilishi mumkin — o'zi, maydonlari, metodlarining parametrlari va o'zi yaratgan ob'ektlar. Zanjir orqali yurish mumkin emas: a.getB().getC().doSomething() — bu buzilish.
LoD — QAYSI ob'ektlarga murojaat qilish mumkinligi haqida (faqat bevosita qo'shnilarga). Tell, Don't Ask — QANDAY murojaat qilish haqida (ma'lumot so'rama, balki qilishni buyur). Ular bir-birini to'ldiradi: LoD muloqot doirasini cheklaydi, Tell Don't Ask — murojaat xarakterini.
LoD DTO (Data Transfer Objects) va mantiqsiz oddiy ma'lumot tuzilmalari uchun buzilishi mumkin. Shuningdek, Builder buzilish hisoblanmaydi, chunki har bir chaqiruv bir xil builder ni qaytaradi. Istisnolar: Stream API dagi zanjirlar (map, filter) — LoD buzilishi emas.
Detekt TooManyFunctions qoidasiga ega (bilvosita), lekin zanjirlarni to'g'ridan-to'g'ri tekshirish uchun DataClassShouldBeImmutable qoidasidan va bindingReference orqali maxsus tekshiruvlardan foydalaning. CI ni sozlang: 2 chaqiruvdan uzun zanjirlar — ogohlantirish, 3 dan uzun — qurilish xatosi.
SwiftLint LoD uchun o'rnatilgan qoidaga ega emas, lekin regex orqali maxsus qoida yaratish mumkin: \..+\.\..+\.\..+ ko'rinishidagi zanjirlar (nuqta orqali 3+ chaqiruv). Alternativa: nimble_operator qoidasidan foydalaning va uni uzun zanjirlarni aniqlash uchun kengaytiring.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.