Har bir dasturchi bilishi kerak bo’lgan S.O.L.I.D. bu – dasturiy ta’minotni ishlab chiqishning ya’ni yozishning asosiy tamoyillari sifatida qabul qilingan.
Dasturchilar, ayniqsa Junior yoki Middle darajada ishga topshirayotganda suhbat vaqtida eng ko’p so’raladigan savollardan biri bu aynan bugungi mavzuimiz hisoblanadi. Shuning uchun ushbu maqolani sabr bilan so’ngigacha diqqat qilib o’qishingizni tavsiya beraman.
Maqola qismlari:
S.O.L.I.D nima?
SOLID dasturiy ta’minot tamoyillari; Bu Robert C. Martin tomonidan ilgari surilgan printsiplar to’plami bo’lib, ishlab chiqilgan dasturiy ta’minot moslashuvchan, qayta foydalanish mumkin, texnik xizmat ko’rsatish va tushunarli bo’lishini ta’minlaydi, kodlarning takrorlanishini oldini oladi. Maykl Feathers tomonidan belgilangan ushbu tamoyillarning maqsadi:
* Siz ishlab chiqayotgan dasturiy ta'minot kelajakdagi talablarga osongina moslashadi,
* Kodni o'zgartirmasdan yangi xususiyatlarni osongina qo'shishimiz mumkin bo'ladi.
* Yangi talablarga qaramay, kodda eng kam o'zgarishlarni ta'minlaydi,
* Shuningdek, doimiy tuzatish yoki hatto kodni qayta yozish kabi muammolar tufayli yuzaga keladigan vaqt yo'qotilishini minimallashtiradi.
Ushbu tamoyillarni qo’llash orqali biz dasturlarimiz o’sib borishi bilan yuzaga keladigan murakkablikning o’sishini oldini olamiz. «Yaxshi kod» yozish uchun siz ushbu tamoyillarni puxta bilishingiz va ularni amalda qo’llashingiz zarur bo’ladi.
S.O.L.I.D nima?
Qisqartma Bo’lmagan Hollari:
S – Single-responsibility principle (Yagona Javobgarlik Printsipi)
Qissadan Hissa: Sinf (obyekt) faqat bitta maqsad uchun o’zgartirilishi mumkin, bu o’sha sinfga yuklanadigan ma’suliyatga kiradi, ya’ni bir sinfning (bu funktsiyaga ham tegishli bo’lishi mumkin) faqat bitta vazifaga ega bo’lishi kerak.
**
O – Open-closed principle (Ochiq-Yopiq Printsipi)**
Qissadan Hissa: Sinf yoki funksiya mavjud xususiyatlarni saqlab qolishi va oʻzgarishlariga ruxsat bermasligi kerak. Ya’ni, u o’z maqsadini o’zgartirmasligi va yangi xususiyatlarga ega bo’lishi mumkin bo’lishi kerak.
**
L — Liskov substitution principle (Liskov Almashtirish Printsipi)**
Qissadan Hissa: Kodimizgda hech qanday o’zgartirish kiritmasdan, ularning Parent (yuqori) sinflari o’rniga Inheritence (pastki) sinflardan foydalanishimiz mumkin bo’lishi kerak.
**
I – Interface segregation principle (Interfeysni Ajratish Printsipi)**
Qissadan Hissa: Barcha mas’uliyatni bitta interfeysga yig’ish o’rniga, ko’proq moslashtirilgan interfeyslarni kiritishimiz kerak.
**
D – Dependency Inversion Principle (Bog’liqlik Inversiyasi Printsipi)**
Qissadan Hissa: Sinflar orasidagi muxtojliklar imkon qadar past bo’lishi kerak, ayniqsa yuqori darajadagi sinflar quyi darajadagi sinflarga muxtoj bo’lmasligi kerak.
Bularni tushinib olgan bo’lsangiz endi har biri haqida alohida kod misollari bilan birga ko’rish vaqti keldi.
Single responsibility prinsipi (Yagona javobgarlik tamoyili) sinflarimiz yagona, aniq belgilangan mas’uliyatga ega bo’lishi kerakligini aytadi. Sinf (obyekt) faqat bitta maqsad uchun o’zgartirilishi mumkin, bu maqsad shu sinfga yuklangan mas’uliyatdir, ya’ni sinfning faqat bitta vazifasi bor.
Agar siz ishlab chiqayotgan sinf yoki funksiya bir nechta maqsadlarga xizmat qilsa, bu sizning qoidaga zid rivojlanish jarayonida ekanligingizni bildiradi. Buni tushunganingizda, uni maqsadlarga muvofiq ravishda to’xtatishingiz kerak.
Talablar o’zgarganda, kodning o’zgarishi kerak bo’lgan qismlari ham bo’ladi. Bu yozma sinflarning (obyektlarning) bir qismi yoki barchasi o’zgartirilganligini anglatadi. Sinf qanchalik ko’p mas’uliyat yuklasa, u shunchalik ko’p o’zgarishlarni boshdan kechirishi kerak. Shunday qilib, kodning ko’p qismlarini o’zgartirishga olib kelganda, qayta yozishda o’zgarishlarni amalga oshirish anchagina qiyinlashadi.
Sinf yoki funktsiyani yozganimizda, uning mas’uliyati yoki maqsadini yaxshi aniqlashimiz va shunga mos ravishda sinfni loyihalashimiz kerak, shunda yuzaga kelishi mumkin bo’lgan har qanday o’zgarishlarni iloji boricha kamroq yangilash va tuzatish orqali kerakli rivojlanishga erishishimiz mumkin. Mas’uliyatni kamaytirish o’zgarishlarga osonroq moslashishni anglatadi.
Test – faqat bitta mas’uliyati bor sinfda test caselar ham ancha kamroq bo’ladi.
Kamroq qaramlik – bitta sinfda yana bitta mas’uliyatga ega bo’lish kamroq qaramlikka olib keladi.
Kam va Aniq tuzilmalar – Kamroq mas’uliyat nozikroq yoki kichikroq tuzilmalarga erishishga imkon beradi. Kichikroq tuzilmalar monolit tuzilmalarga qaraganda ancha foydali va kod ravshanligini/o’qilishini oshiradi.
class User {
int? _id;
String? _name;
String? _street;
String? _city;
String? _username;
//Getters, setters
void changeAddress(String street,String city) {
//logic
}
void login(String username) {
//logic
}
void logout(String username) {
//logic
}
}
- Bu orada Adressga tegishli city va streed kabi o’zgaruvchilar to’g’ridan-to’g’ri User sinfida bo’lishi kerakmidi?
Har qanday qo’shimcha manzil ma’lumotlari so’ralganda (masalan, country yoki postalCode), bu yerda User sinfiga to’liq ta’sir qiladi. User sinfiga u bevosita javobgar bo’lmagan amal ta’sir qiladi. «_street», «_city» kabi o’zgaruvchilar faqat manzil uchun talab qilinadi, shuning uchun biz Adress deb nomlangan yangi sinf yaratsak qanday bo’larkin?
class LoginService{
void login(String username) {
//log-in logic
}
void logout(String username) {
//log-out logic
}
}
SignIn va SignOut kabi amallar, user ma’lumotlari qanchalik zarur bo’lishidan qat’i nazar, User sinfining javobgarligi emas. Biz bu funktsiyalarni alohida sinfga o’tkazishimiz kerak. Shunday qilib, biz foydalanuvchi sinfidan kirish amallarini tozalaymiz va bitta javobgarlik uchun LoginServiceni kiritamiz. Aytaylik, User sinfining changeAddress qismini o’zgartiramiz. Bizda yana yomon misol bor.
class AddressService{
void changeAddress(Account account) {
//logic
}
}
Bu sinf va funktsiya doirasida qilgan har bir narsa faqat Adressga tegishli bo’lishi kerak. Shuning uchun Account bu qismga kiritilmasligi kerak. Chunki funktsiya faqat manzilni o’zgartirish bilan shug’ullanadi. Bizda yuqorida aytib o’tgan yangi Address sinfi bor va keling, bundan foydalanaylik, bu holda biz to’g’ri ish qilgan bo’lamiz.
class User {
int? _id;
Address? _name;
//Getter,setter
}
va
class Address {
String _street;
String _city;
String _country;
//Getter,setter
}
yangi yuzaga kelgan holatda
class AddressService{
void changeAddress(Address address) {
// Faqatgina Adress bilan shug'ullanaman va ma'suliyatim undan. Account bilan ishim yo'q.
//logic
}
}
Mas’uliyatni har bir sinfga samarali ajratdik, shu bilan kodni bir joyda o’zgartirish imkoniyatini va kodning istalgan joyini ikkinchisini buzish imkoniyatini kamaytirdik. Yangilanishlar kerak bo’lganda osongina integratsiyalashimiz mumkin bo’lgan tuzilmani yaratdik.
Har bir sinf va funksiya uchun bitta ish kelajakdagi muammolarni oldini oladi, aniq kod yozish imkonini beradi va dasturiy ta’minotingizga yangi xususiyatlarni qo’shishni osonlashtiradi. Hozirgacha yozgan kodingizni ko’rib chiqing va bundan keyin yozgan kodingizda bu tamoyilni e’tiborsiz qoldirmang.
Open/Closed prinsipi. Sinf yoki funksiya mavjud xususiyatlarni saqlab qolishi va oʻzgarishlariga ruxsat bermasligi kerak. Ya’ni, u o’z maqsadini o’zgartirmasligi va yangi xususiyatlarga ega bo’lishi mumkin bo’lishi kerak.
Bu tamoyil barqaror va qayta foydalanish mumkin bo’lgan tuzilmada kod yozishning asosini tashkil qiladi.
Robert C. Martin
Open – sinf uchun yangi funksiyalarni qo’shish imkonini beradi. Talablar o’zgarganda, yangi talablarni qondirish uchun sinfga yangi yoki turli funksiyalar qo’shilishi mumkin.
Abstract Sinfning asosiy xususiyatlarini o’zgartirish mumkin bo’lmasligi kerak.
Closed – Mavjud kodni o’zgartirmasdan yangi kod yozish orqali biz ishlab chiqqan dasturiy ta’minot/sinfga yangi xususiyatlarni qo’shish mumkin bo’lishi kerak. Agar siz yangi talab kiritilganda mavjud kodga biron bir o’zgartirish kiritayotgan bo’lsangiz, open/close printsipni buzayotganingizni tekshirish siz uchun foydali bo’ladi. Dasturiy ta’minotni ishlab chiqishda biz kelajakdagi xususiyatlar va yangilanishlarni to’liq bashorat qila olmaymiz. Shuning uchun, biz yuzaga kelishi mumkin bo’lgan holatlarni hozirda yozib chiqmasligimiz kerak. (KISS prinsipiga qarang) Mavjud kodni o’zgartirmasdan, mavjud tuzilmani buzmasdan, kelajakdagi yangi xususiyatlar uchun moslashuvchan rivojlanish modelini va old tomondan ochiq va kelajakdagi talablarga osongina moslashadigan modelni amalga oshirishimiz kerak.
Aytaylik, Single Responsibility bilan maydon hisoblash dasturini yozishni boshlaymiz, ammo bizga faqat to’rtburchaklar ma’lumotlari kerak.
class Rectangle {
double _length;
double _height;
// getters/setters ...
}
AreaService.dart
class AreaService {
double calculateArea(List<Rectangle>... shapes) {
double area = 0;
for (Rectangle rect in shapes) {
area += (rect.getLength() * rect.getHeight());
}
return area;
}
}
Aytaylik, AreaServiceimiz to’rtburchakni hisoblash darajasida, lekin bizning ehtiyojlarimiz doirani hisoblashni ham talab qila boshladi.
class Circle {
double _radius;
// getters/setters
}
Doira maydonini hisoblash imkoniyatiga ega bo’lish uchun AreaService-dagi calculateArea qismiga o’zgartirishlar kiritishimiz kerak edi.(Ko’rib turganingizdek ishlar hech ham yaxshi ketmayapti
)
class AreaService {
double calculateArea(List<Object> shapes) {
double area = 0;
for (Object shape in shapes) {
if (shape instanceof Rectangle) {
Rectangle rect = (Rectangle) shape;
area += (rect.getLength() * rect.getHeight());
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
area += circle.getRadius() * cirlce.getRadius() * Math.PI;
} else {
throw new RuntimeException("Shape not supported");
}
} return area;
}
}
Uchburchak kabi yangi shakl qo’shmoqchi bo’lganimizda, doimo bu usulga o’zgartirishlar kiritamiz va vaziyat yomonlashadi va vaziyat open/close tamoyilga amal qilmayotganimizni ko’rsatadi. Bu holat uchun bizning sinfimiz/usulimiz o’zgartirish uchun yopiq emas, aksincha, o’zgartirish majburiy bo’lib qoldi. Kengayish variantlar orasida emas. Biz qo’shiladigan har bir yangi shakl uchun AreaService-ni o’zgartirishimiz kerak. Qanday qilib uni Ochiq/Yopiq tamoyiliga moslashtira olamiz? Bunga erishish uchun, avvalo, bizda nima bor va nima qilishni xohlayotganimizga e’tibor qaratishimiz kerak. AreaService barcha shakl turlarining maydonini hisoblash uchun javobgardir, ammo har bir hududning o’z hisoblash usuli mavjud. Agar bizda buni hal qilish uchun Shape interfeysi bo’lsa va har bir shakl uchun hisoblangan maydonni return qilsak nima bo’ladi? Shunday qilib, biz obyekt turidan o’zimiz biladigan ob’ektga o’tgan bo’lamiz.
class Shape { // Faqat dart dasturlash tilida interface yo'q. Uni biz with orqali mixin qilib chiqarishimiz mumkin.
double getArea();
}
Har bir shakl uchun Shape dan Inheritence olinishi kerak. Bu yerda buni aniq ko’ramizki, Shapelardan biri bo’lgan To’rtburchaklar maydonini hisoblashni getArea usulim bilan ishlatishim mumkin.
class Rectangle implements Shape {
double? _length;
double? _height;
// getters/setters …
@override
double getArea() {
return (length * height);
}
}
va Circle uchun ham ayni shu ishlarni amalga oshiramiz
class Circle implements Shape {
double _radius;
// getters/setters …
@override
double getArea() {
return (radius * radius * Math.PI);
}
}
Keling, AreaManager-ga o’tamiz va bizning calculateArea funksiyamiz qanday yaxshi ko’rinishini ko’ramiz. Dasturchi chiroyli va ehtiyotkorlik bilan kod yozsa, o’zini ancha yaxshi his qiladi va o’z ishidan faxrlanadi.
class AreaManager {
double calculateArea(List<Shape> shapes) {
double area = 0;
for (Shape shape in shapes) {
area += shape.getArea();
}
return area;
}
}
Endi bizning dasturimiz Open/Closed tamoyiliga mos keladi. Har qanday yangi shakl maydonini hisoblashimiz kerak bo’lganda, biz AreaManager-ni o’zgartirmasligimiz kerak, biz o’zgartirish uchun yopiq bo’lishimiz kerak. Shakl obyektimizdan yangi shaklni olamiz va uning ichida maydon hisobini amalga oshiramiz. Shunday qilib, biz kengayish uchun ochiqmiz va biz hech qanday joyda o’zgartirish kiritishimiz shart emas.
S.O.L.I.D haqidagi bu maqola hammangizga manzur keldi degan umiddaman. Qolgan qismlari uchun ham yana maqola chiqarishga harakat qilaman. Bu maqolani yozish uchun bir qancha manbalardan foydalanildi. E’tiboringiz uchun raxmat. Va yana ko’rishguncha kodlaringizda omad !