9.1 Dependency Inversion
Eslaysanmi, biz ilgari gaplashgan edik, server ilovasida new Thread().start() orqali oqimlar yaratib bo'lmaydi? Oqimlarni faqat konteyner yaratishi kerak. Endi bu fikrni yanada kuchaytiramiz.
Barcha obyektlarni ham faqat konteyner yaratishi kerak. Albatta, bu barcha obyektlarga emas, balki biznes obyektlarga taalluqli. Ularni ba'zida binlar deb ham atashadi. Bu yondashuvning asosi SOLID'ning beshinchi printsipidan keladi, bu sinflardan qutulib, interfeyslarga o'tishni talab qiladi:
- Yuqori darajadagi modullar past darajadagi modullarga bog'liq bo'lmasligi kerak. Ikkalasi ham abstraksiyalarga bog'liq bo'lishi kerak.
- Abstraksiyalar tafsilotlarga bog'liq bo'lmasligi kerak. Realizatsiya abstraksiya asosida bo'lishi kerak.
Modullar aniq realizatsiyalarga havola qilmasligi kerak va barcha bog'liqliklar va ular orasidagi o'zaro aloqalar faqat abstraksiyalar (ya'ni interfeyslar) asosida tashkil etilishi kerak. Bu qoida mazmunini bitta jumlada izohlash mumkin: barcha bog'liqliklar interfeyslar ko'rinishida bo'lishi kerak.
Ushbu qoida o'zining fundamental va sodda ko'rinishiga qaramasdan, ko'pincha buziladi. Ya'ni, har safar dasturning/modulning kodida new operatoridan foydalanib, aniq turdagi yangi obyekt yaratganimizda, interfeysga bog'liqlik o'rniga realizatsiyaga bog'liqlik paydo bo'ladi.
Buni oldini olish va obyektlarni qaerda yaratish kerakligini minimum darajada saqlash va sinflarni aniq ko'rsatilgan joylarni bir joyda to'plash kerak, shunda ular dasturning kodida tarqalib ketmaydi.
Yaxshi yechim - yangi obyektlarning yaratilishini ixtisoslashgan obyektlar va modullar doirasida jamlash g'oyasi. Bu fabrika, service locator, IoC-konteynerlar kabi.
Bu ma'lum bir ma'noda Yagona tanlov Printsipiga (Single Choice Principle) ergashadi, ya'ni: "Dasturiy tizim bir nechta muqobilni qo'llab-quvvatlashi kerak bo'lganda, ularning to'liq ro'yxati tizimning faqat bitta moduli tomonidan ma'lum bo'lishi kerak".
Shuning uchun, agar kelajakda yangi variantlar qo'shilishi kerak bo'lsa (yoki yangi realizatsiyalar, yangi obyektlar yaratish misolida), faqat u ma'lumotlarni o'z ichiga olgan modulni yangilash kifoya qiladi, qolgan barcha modullar zarar ko'rmaydi va o'z faoliyatini davom ettirishi mumkin.
Misol 1
new ArrayList o'rniga List.new() yozish mantiqiyroq bo'lardi, JDK sizga to'g'ri list realizatsiyasini taklif qiladi: ArrayList, LinkedList yoki hatto ConcurrentList.
Masalan, kompilyator obyektga turli oqimlardan murojaatlar bo'lishini ko'rib, u yerdan oqim xavfsiz realizatsiya qo'yadi. Yoki list o'rtasiga juda ko'p qo'yish bo'lsa, unda realizatsiya LinkedList asosida bo'ladi.
Misol 2
Bu allaqachon sortirovkalar bilan sodir bo'lgan. Oxirgi marta, qachon sen kolleksiyani sortirovka qilish uchun sortirovka algoritmi yozgan eding? Buning o'rniga endi hamma Collections.sort() metodidan foydalanadi, va kolleksiya elementlari Comparable interfeysini (taqqoslanadigan) qo'llab-quvvatlashi kerak.
Agar sort() metodiga 10 dan kam elementli kolleksiya o'tkazilsa, uni Bubble sort (Pufakcha sortirovka) bilan ham tartiblash mumkin, Quicksort emas.
Misol 3
Kompilyator allaqachon sen qatorlarni qanday birlashtirayotganingni kuzatadi va kodingni StringBuilder.append() ga o'zgartiradi.
9.2 Dependency Inversion amalda
Endi eng qiziqarli: biz qanday qilib nazariyani va amaliyotni birlashtirish haqida o'ylab ko'ramiz. Qanday qilib modullar to'g'ri ravishda o'zlarining “bog'liqliklarni” yaratishi va qabul qilishi mumkin va Dependency Inversion ni buzmasligi kerak?
Bu uchun modulni loyihalashda sen o'zing uchun qaror qilishing kerak:
- modul nima qiladi, qanday funksiyani bajaradi;
- moduldan uning atrof muhitidan nima kerak, ya'ni qanday obyektlar/modullar bilan muomala qilishi kerak;
- va u bu narsalarni qanday qilib oladi.
Dependency Inversion prinsiplarini qo'llash uchun albatta sen modulning qanday tashqi obyektlardan foydalanishini va ularga qanday qilib havolalarni olishini aniqlab olishing kerak.
Bu yerda quyidagi variantlar mumkin:
- modul obyektlarni o'zi yaratadi;
- modul obyektlarni konteynerdan oladi;
- modul obyektlar qayerdan kelganini bilmaydi.
Muammo shundaki, obyekt yaratish uchun aniq turdagi konstruktorga chaqirish kerak, va natijada modul interfeysga emas, balki aniq realizatsiyaga bog'lanadi. Ammo agar biz modul kodida obyektlarni to'g'ridan-to'g'ri yaratishni xohlamasak, Factory Method (Fabrika Metodi) naqshini ishlatishimiz mumkin.
"Mazmuni shundaki, new orqali obyektni bevosita initsializatsiya qilish o'rniga, biz klass-klientga obyektlarni yaratish uchun ba'zi interfeys taqdim etamiz. Bunday interfeys to'g'ri dizaynda har doim o'zgartirilishi mumkin bo'lganligi sababli, biz yuqori darajadagi modullarda past darajadagi modullardan foydalanishda ma'lum bir moslashuvchanlikni qo'lga kiritamiz".
O'zaro bog'liq bo'lgan ob'ektlar to'plamini yoki oilasini yaratish kerak bo'lganda, Factory Method o'rniga Abstract Factory (Abstrakt Fabrika) ishlatiladi.
9.3 Service Locator dan foydalanish
Modul o'zi kerakli obyektlarni allaqachon bor bo'lganlardan oladi. Sistema ichida obyektlar repository mavjud bo'lishi kerak, bunda modullar o'z obyektlarini qo'yishlari va repositorydan obyektlarni olishlari mumkin.
Bu yondashuv Service Locator naqshini amalga oshiradi, bu yondashuvning asosiy g'oyasi shundan iboratki, dasturda barcha bog‘liqliklarni (servislarni) olishni biladigan ob'ekt mavjud.
Fabrikalardan asosiy farqi shundaki, Service Locator obyektlarni yaratmaydi, balki allaqachon mavjud bo'lgan obyektlarni o'z ichiga oladi (yoki qayerdan/qanday olishni biladi, agar yaratadigan bo'lsa, faqat ilk chaqiruvda). Fabrika har bir chaqiruvda yangi obyekt yaratadi, siz uni to'liq egalikka olasiz va u bilan nima istasangiz, shuni qilishingiz mumkin.
Muhim! Service Locator allaqachon mavjud bo'lgan obyektlar manzillarini beradi. Shuning uchun Service Locator tomonidan berilgan obyektlar bilan juda ehtiyot bo'lish kerak, chunki ular bilan birga boshqa birov ham ishlashi mumkin.
Service Locator obyektlarini to'g'ridan-to'g'ri konfiguratsiya fayli orqali qo'shish mumkin va umuman dasturchiga qulay bo'lgan har qanday usul bilan. Har qanday sinf o'zining statik metodlari yoki interfeysi bo'lgan statik sinf bo'lishi mumkin va kerakli sinflarga konstruktor yoki metod orqali o'tkaziladi.
Service Locator ba'zida antipattern deb ataladi va undan foydalanishni tavsiya etilmaydi (chunki bu yashirin bog'liqliklarni yaratadi va yaxshi dizayn ko'rinishini beradi). Mark Simanning maqolasida batafsil o'qishingiz mumkin:
9.4 Dependency Injection
Modul “bog'liqlarni” olish haqida umuman qayg'urmaydi. U faqat o'zi uchun nima kerakligini aniqlaydi, va barcha kerakli bog'liqlarni unga tashqi tomondan (kimgadir boshqa) ta'minlanadi (kiritiladi).
Bu shunday ham ataladi — Vnidrush Zavisemostey (Dependency Injection). Odatda kerakli bog'liqliklar konstruktor parametrlari (Constructor Injection) yoki klass metodlari orqali beriladi (Setter injection).
Bunday yondashuv bog'liqlik yaratish jarayonini inverta qiladi — o'rniga bog'liqlik yaratishni boshqa kimtadir boshqaradi. Modul faol ob'ektlar emissiyasi o'rniga passiv bo'ladi — u yaratmaydi, balki uning uchun boshqalar yaratadi.
Bunday harakat yo'nalishini o'zgartirish Inversion of Control (Boshqaruv Inverziyasi), yoki Gollivud Printsipi — “Bizga qo'ng'iroq qilmang, biz o'zimiz sizga qo'ng'iroq qilamiz” deb ataladi.
Bu eng moslashuvchan yechim, modullarga eng katta avtonomiyani beradi. Aytish mumkinki, faqat bu “Yagona mas'uliyat printsipi” ni to'liq amalga oshiradi — modul o'z vazifasini yaxshi bajarishga to'liq e'tiborli bo'lishi va boshqa narsalar haqida qayg'urmasligi kerak.
Modulni barcha kerakli narsalar bilan ta'minlash — bu alohida vazifa, va bu mos "mutaxassis" (odatda bog'liqlarni va ularni kiritishni boshqaradigan konteyner — IoC-konteyner) tomonidan amalga oshirilishi kerak.
Aslida, bu hayotda bo'lgani kabi: yaxshi tashkil etilgan kompaniyada dasturchilar dasturchilik qiladi, stollar, kompyuterlar va ular uchun zarur bo'lgan barcha narsalarni ofis-menejeri sotib olib, ta'minlaydi. Yoki, agar dasturni konstruktor metaforasi sifatida foydalansak, modul simlar haqida o'ylamasligi kerak, konstruktni yig'ish kimdir boshqa tomonidan amalga oshiriladi, detallar emas.
Interfeyslarni modullar o'rtasidagi bog'liqliklar uchun tavsif qilish (Dependency Inversion) + ushbu bog'liqlarni to'g'ri yaratish va kiritish (eng avvalo Dependency Injection) bog'lanishni kamaytirishning kalit texnikalari ekanligini aytish haddan tashqari bo'lmaydi.
Ular kodning bog'lanish zaifligini saqlaydigan, uning moslashuvchanligi, o'zgarishlarga chidamliligi, qayta foydalanishi, va bu holda boshqa barcha texnikalar kam ahamiyatli bo'lgan asosdir. Bu bog'lanish zaifligi va yaxshi arxitekturaning asosi.
Inversion of Control printsipi (Dependency Injection va Service Locator bilan birga) Martin Fowler tomonidan batafsil tahlil qilingan. Uning ikkala maqolasining tarjimasi mavjud: "Inversion of Control Containers and the Dependency Injection pattern" va “Inversion of Control”.
GO TO FULL VERSION