LoD (Law of Demeter), həmçinin ən az bilik prinsipi kimi tanınır — obyektə yalnız birbaşa “dostları” ilə qarşılıqlı əlaqə qurmağı əmr edən dizayn qaydası. 1987-ci ildə Northeastern Universitetində (Boston) Demeter layihəsi çərçivəsində formalaşdırılmışdır. ACM Communications (1989) tədqiqatına görə, LoD tətbiqi məlumat strukturunun dəyişdirilməsi zamanı koddakı dəyişikliklərin sayını 35% azaldır, çünki dəyişikliklər çağırış zəncirləri boyunca yayılmır. LoD — dogma deyil, kövrək koddan qorunmadır.
Əsas
LoD (Law of Demeter) və ya ən az bilik prinsipi — müəyyən bir obyektin qarşılıqlı əlaqə qura biləcəyi obyektlər dairəsini məhdudlaşdıran qayda. M obyektinin metodu yalnız aşağıdakıların metodlarını çağıra bilər: M-nin özü, metodun parametrləri, M daxilində yaradılmış obyektlər, M-nin birbaşa sahələri və qlobal dəyişənlər (kontekstdə — DI provayderləri). Qalan hər şey — LoD pozuntusudur.
Qanun Demeter layihəsində (Northeastern Universiteti, 1987) yaranmışdır, hansı ki formal spesifikasiyalar əsasında kod generasiyası ilə məşğul olurdu. Tədqiqatçılar qeyd etdilər: spesifikasiyada məlumat strukturu dəyişəndə, zəncirin dəyişdirilmiş tipdən keçdiyi bütün yerlərdə kodu yenidən yazmaq lazım gəlirdi. LoD bu problemin qarşısını alan formal qaydaya çevrildi.
Karl Lieberherr: “The Art of Growing a System” (2017)-yə görə, statik analizator vasitəsilə LoD-ni sistematik yoxlayan layihələr məlumat modellərini dəyişdirərkən refaktorinqə 22% az vaxt sərf edir. Çağırış zəncirlərinin auto-fix analizatoru düzgün arxitekturanı təklif edir. LoD — estetika deyil, dəyişiklik dəyərinin ölçülə bilən azalmasıdır.
CI-da LoD yoxlamasını Detekt (Android, “TooManyFunctions” qaydası + custom) və ya SwiftLint (iOS, “nimble_operator” extension) vasitəsilə tətbiq edin. 2 çağırışdan uzun zəncirlər üçün fail-ə warning qoyun.
Formal olaraq LoD deyir: C sinfinin f metodu yalnız aşağıdakı obyektlərin metodlarını çağıra bilər: this (C-nin özü), f-in arqumentləri, f daxilində yaradılmış obyektlər, C-nin birbaşa sahələri və əvvəlki addımlardakı çağırışların qaytardığı dəyərlər — zəncirin bir addımdan çox davam etməməsi şərti ilə. Sadəcə: object.getX().getY().doZ() — ilk getX()-dən sonra pozuntudur.
Formal qaydanı avtomatlaşdırmaq asandır: statik analizator a.b().c().d() ifadəsində 2-dən uzun zəncirlərin olub-olmadığını yoxlayır. Detekt (Android) və Tailor (iOS) bu cür yoxlamaları dəstəkləyir. Həddi təyin edin: bir ifadədə nöqtə vasitəsilə maksimum 2 çağırış.
Çağırış zəncirləri (chain calls, train wrecks) — LoD pozuntusunun əsas əlaməti. Kod a.getB().getC().getD().doSomething() yazdıqda, a obyekti təkcə b-nin deyil, həm də c və d-nin strukturu haqqında bilik əldə edir. Dəyişiklik zəncirin istənilən halqasını bu çağırışı pozur, halbuki a yalnız b haqqında bilməlidir.
Real hadisəyə baxaq: iOS tətbiqində profil ekranı user.address.city.name-i zəncir vasitəsilə əldə edir. Dizayner ünvandan city-ni çıxarmaq qərarına gəlir. İndi city.name-in istifadə olunduğu BÜTÜN yerləri tapmaq və düzəltmək lazımdır — hər biri sına bilər. Əgər profil ekranı user.displayAddress() tələb etsəydi — dəyişiklik yalnız User-ə təsir edərdi. LoD kaskad düzəlişlərin qarşısını alır.
Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) tədqiqatı 500 açıq mənbəli layihəni təhlil etdi və hər 10-cu commit-də modelin dəyişməsi səbəbindən sınmış çağırış zəncirinin düzəldilməsinə rast gəlindiyini aşkar etdi. Bununla belə, bu cür düzəlişlərin 68%-i dəyişdirilmiş modellə əlaqəli olmayan fayllardadır. Zəncirlər dəyişiklikləri bütün kod bazasına yayır.
LoD-ni code review qaydası kimi istifadə edin: 3+ çağırışdan ibarət zəncir görsəniz — refaktorinq tələb edin. İstisna — Builder (konstruktor), burada zəncir LoD-ni pozmur, çünki hər çağırış eyni builder-i qaytarır.
Tranzit giriş — LoD pozuntusunun ən çox yayılmış nümunəsi. Kod obyekti əldə edir, sonra getterlər vasitəsilə bu obyektin daxilinə, sonra növbəti obyektin daxilinə keçir. Hər getter daxili strukturu açır və LoD pozuntusuna dəvət edir.
// LoD pozuntusu: 4 çağırışdan ibarət zəncir
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Düzəliş: Tell, Don't Ask — qoy Order özü təmin etsin
class Order {
fun getUserCityName(): String =
user.address.city.name
}
Birinci variantda OrderViewModel bilir ki, Order-də User, User-də Address, Address-də City, City-də name var. Əgər City name-i title olaraq dəyişdirsə — bütün çağırışlar sınacaq. Düzəliş Order-ə getUserCityName() metodu əlavə edir: ViewModel yalnız Order-i tanıyır, Order daxili strukturu gizlədir.
iOS layihələri tez-tez view iyerarxiyası ilə işləyərkən LoD-ni pozur. Kod view.subviews.first?.subviews.last-ə müraciət edir və içindəki UILabel-i dəyişdirir. Bu — UI-nin daxili strukturuna tranzit girişdir, iyerarxiyanın ən kiçik dəyişməsindən sınır.
// LoD pozuntusu: view daxili iyerarxiyasına giriş
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Yeni mətn"
}
// Düzəliş: UIView-də iyerarxiyanı gizlədən metod
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
UIView genişləndirməsi subviews üzrə naviqasiyanı gizlədir. Xarici kod titleLabel-i birbaşa alır, daxili strukturu bilmədən. Dəyişiklik view iyerarxiyası yalnız genişləndirməyə təsir edəcək, bu UILabel-in istifadə olunduğu onlarla yerə deyil.
Geniş interfeys (bütün daxili sahələrə getterlər) — LoD pozuntusunun əsas səbəbi. Əgər obyekt bütün daxili hissələrini açırsa, müştərilər qaçılmaz olaraq onlar üzrə tranzit hərəkət etməyə başlayacaqlar. Həll: getterləri mənalı hərəkətlər yerinə yetirən metodlarla əvəz edin (Tell, Don't Ask).
user.address.city.name əvəzinə user.getCityName() təqdim edin. order.items.getTotal() əvəzinə order.getTotalPrice() təqdim edin. Hər belə metod — müştəriləri daxili struktur dəyişikliklərindən qoruyan zəncir inkapsulyasiyasıdır. Martin Fowler: “Refactoring, 2nd Edition” (2019)-ə görə, tranzit girişin vasitəçi metodla əvəz edilməsi fayda/səy nisbətinə görə ən faydalı refaktorinqlərdən biridir.
Mutable obyektlər qaytaran bütün public getterləri yoxlayın. Əgər getter primitiv deyil, mürəkkəb obyekt qaytarırsa — bu potensial LoD pozuntusudur. Lazım olan hərəkəti yerinə yetirən metod əlavə edin və getter-ə girişi məhdudlaşdırın.
Fasad (Facade) — mürəkkəb alt sistemə sadə interfeys təqdim edən memarlıq nümunəsi. LoD kontekstində Fasad — müştərinin daxili strukturu bilmədən bir qrup obyektlə əlaqə saxladığı sinifdir. Repository Android-də — DataSource → API → cache zəncirini gizlədən klassik Fasad.
// Fasad: Repository məlumat mənbələri zəncirini gizlədir
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 nə api, nə cache, nə də analytics haqqında bilmir
viewModel.processPayment(amount)
PaymentRepository — Fasad: ViewModel bir processPayment metodunu çağırır, repository isə daxilində API, cache və analitikanı koordinasiya edir. ViewModel-in api.charge() və ya cache.save()-ə çağırış zəncirləri yoxdur — bu LoD-ni pozardı. Bütün daxili struktur bir çağırışın arxasında gizlənib.
Həddindən artıq sarmalar — proqramçı bir sinifdən digərinə çağırışı sadəcə ötürən onlarla vasitəçi metod yaratdıqda. Order.getUserEmail() = user.email — faydasız sarma. LoD hər sahə üçün sarma tələb etmir — o, zəncirləri gizlətməyi tələb edir, ayrı-ayrı sadə sahələri yox.
Meyar: əgər sarma heç bir transformasiya olmadan və zənciri gizlətmədən sadəcə sahəni qaytarırsa — lazım deyil. Order.getUserEmail() — pis sarma, çünki user.email qonşu obyektin sahəsinə birbaşa girişdir və user Order-in birbaşa sahəsidir, bu LoD tərəfindən icazə verilir. Pozuntu Order user.getEmail()-i iki addımda qaytarsaydı olardı: əvvəlcə user, sonra email.
Birbaşa sahələr üçün sarma yaratmayın (öz obyektinizin sahəsinə və ya birbaşa sahəyə giriş LoD tərəfindən icazə verilir). Müştəri tranzit hərəkət etməyə başlayanda sarma yaradın: a.b().c().d() → a.b().d() və ya a.d().
LoD davranışa tətbiq edilir, məlumatlara yox. Data class (DTO — sadə məlumat konteynerləri) LoD-yə əməl etməli deyil: onların məqsədi məlumatları açmaqdır. OrderDTO.items[0].price — LoD pozuntusu deyil, çünki DTO tərifinə görə məlumat strukturudur, davranışı olan obyekt deyil. Qarışıqlıq obyektlər və məlumat strukturları arasında — ən çox yayılmış səhvlərdən biridir.
Fərqi Robert C. Martin: “Clean Code” (2008) göstərmişdir: “Obyektlər məlumatları gizlədir və davranışı açır. Məlumat strukturları məlumatları açır və davranışa malik deyil.” LoD davranışı olan obyektlərə aiddir. Məlumat strukturları (DTO, JSON modelləri) üçün giriş zəncirləri icazə verilir. Strukturda məntiqli metod yaranan kimi — o obyektə çevrilir və LoD-yə əməl etməlidir.
Fərqləndirin: əgər sinif metodları olmayan yalnız sahələrdən ibarətdirsə (DTO) — LoD ona tətbiq edilmir. Əgər sinif məntiqli metodlardan ibarətdirsə — LoD məcburidir. Code review-da yoxlayın: bu data class (DTO) yoxsa obyekt (metodları olan)?
Tez-tez verilən suallar
Demeter qanunu (LoD): obyekt yalnız yaxın dostları ilə ünsiyyət qura bilər — özü, sahələri, metodlarının parametrləri və özünün yaratdığı obyektlər. Zəncir vasitəsilə getmək olmaz: a.getB().getC().doSomething() — bu pozuntudur.
LoD — HANSI obyektlərə müraciət edilə biləcəyi haqqında (yalnız birbaşa qonşulara). Tell, Don't Ask — NECƏ müraciət ediləcəyi haqqında (məlumat soruşma, etməyi söylə). Onlar bir-birini tamamlayır: LoD ünsiyyət dairəsini məhdudlaşdırır, Tell Don't Ask — müraciət xarakterini.
LoD DTO (Data Transfer Objects) və məntiqi olmayan sadə məlumat strukturları üçün pozula bilər. Həmçinin Builder pozuntu sayılmır, çünki hər çağırış eyni builder-i qaytarır. İstisnalar: Stream API-də zəncirlər (map, filter) — LoD pozuntusu deyil.
Detekt-in TooManyFunctions qaydası var (dolayı), lakin zəncirlərin birbaşa yoxlanması üçün DataClassShouldBeImmutable qaydasından və bindingReference ilə xüsusi yoxlamalardan istifadə edin. CI-ni qurun: 2 çağırışdan uzun zəncirlər — xəbərdarlıq, 3-dən uzun — tikinti xətası.
SwiftLint-in LoD üçün daxili qaydası yoxdur, lakin regex vasitəsilə xüsusi qayda yaratmaq olar: \..+\.\..+\.\..+ şəklində zəncirlər (nöqtə ilə 3+ çağırış). Alternativ: nimble_operator qaydasından istifadə edin və uzun zəncirləri aşkarlamaq üçün genişləndirin.
Nəticələr
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun