SOLID սկզբունքները
Օբյեկտակողմնորոշված ծրագրավորումը(OOP) ծրագրային ապահովման մշակման մեջ՝ պրոյեկտների ձևավորման համար, ստեղծեց նոր մոտեցումներ։ Մասնավորապես,OOP-ն ծրագրավորողներին թույլ է տվել համատեղել ընդհանուր նպատակին կամ գործառույթներին ծառայող սուբյեկտները առանձին class-ներում, որոնք նախատեսված են ինքնուրույն խնդիրները լուծելու համար և անկախ են պրոյեկտի այլ մասերից:Այնուամենայնիվ, միայն OOP-ի օգտագործումը չի նշանակում, որ ծրագրավորողը ապահովված է վատ, դժվար կարդացվող կոդ գրելու հնարավորությունից: Ռոբերտ Մարտինը մշակել է օբյեկտակողմնորոշված ծրագրավորման և ձևավորման հինգ սկզբունքներ, որոնք կօգնեն բոլոր ցանկացողներին մշակել OOP պրոյեկտներ։ Այդ սկզբունքները կոչվում են SOLID:
Ին՞չ է SOLID-ը
Ահա ինչպես է բացվում SOLID հապավումը․
[S]. Single Responsibility Principle (Միակ պատասխանատվության սկզբունք)
[O]. Open-Closed Principle (Բաց-փակ սկզբունք)
[L]. Liskov Substitution Principle (Լիսկովի փոխարինման սկզբունք)
[I]. Interface Segregation Principle (Ինտերֆեյսի առանձնացման սկզբունք)
[D]. Dependency Inversion Principle (Կախվածության ինվերսիայի սկզբունք)
Միակ պատասխանատվության սկզբունք(Single Responsibility Principle)
Յուրաքանչյուր class պետք է լուծի միայն մեկ խնդիր։ Class-ը պետք է
պատասխանատու լինի միայն մեկ խնդրի լուծման համար։
Եթե class-ը նախատեսված է մի քանի խնդիրների լուծման համար, ապա պարզվում է, որ դրա ենթահամակարգերը,որոնք իրականացնում են այդ խնդիրների լուծումը, փողկապակցված են միմյանց հետ։Այդպիսի մի ենթահամակարգի փոփոխությունը բերում է մնացած ենթահամակարգերի փոփոխության։ Այս սկզբունքը կիրառելի է ոչ միայն class-ների, այլ նաև ծրագրային այլ կոմպոնենտների համար։
Օրինակ․
Վերը ներկայացված Car class-ը նկարագրում է ինչ որ ավտոմեքենա։ Այս class-ը խախտում է Միակ պատասխանատվության (Single Responsibility) սկզբունքը։ Single Responsibility սկզբունքի համաձայն class-ը պետք է լուծի մեկ խնդիր։ Այն ինչ մեր օրինակի class-ը լուծում է երկու խնդիր, զբաղվում է տվյալների պահպանմամբ (method save) և այդ տվյալների մանիպուլացիայով (method getName):
Ին՞չ խնդրի կարող է սա հանգեցնել։
Եթե փոխենք տվյալների բազայի հետ աշխատելու կարգը , ապա պետք է
փոփոխություններ կատարենք բոլոր այն class-ներում, որոնք աշխատում են տվյալների բազայի հետ։Այսպիսի ճարտարապետությունը ճկուն չէ․ փոփոխելով մեկ ենթահամակարգը մենք ազդում ենք մյուս ենթահամակարգերի վրա, ինչը հիշեցնում է դոմինոյի էֆեկտը։
Որպեսզի վերոնշյալ կոդը համապատասխանեցնենք միակ
պատասխանատվության (Single Responsibility) սկզբունքին, կստեղծենք ևս մեկ class, որի միակ խնդիրը կլինի բազաների հետ աշխատանքը, մասնավորապես՝ բազայում class-ի օբյեկտներ պահպանելը։
Ահա թե ինչ է ասում Սթիվ Ֆենտոնը այս մասին. «Class-ներ մշակելիս մենք պետք է ձգտենք միավորել միանման կոմպոնենտները, այսինքն այն կոմպոնենտները, որոնցում փոփոխությունները տեղի են ունենում նույն պատճառներով։ Իսկ այն կոմպոնենտները, որոնցում փոփոխությունները տեղի են ունենում տարբեր պատճառներով, պետք է փորձենք առանձնացնել»։
Միակ պատասխանատվության(Single Responsibility) սկզբունքի ճիշտ
կիրառումը հանգեցնում է մոդուլի ներսում տարրերի միացման բարձր աստիճանի, այսինքն ` այն փաստին, որ դրա ներսում լուծելի առաջադրանքները լավ են համապատասխանում դրա հիմնական նպատակին:
Բաց-փակ սկզբունք(Open-Closed Principle)
Ծրագրային սուբյեկտները (դասեր, մոդուլներ, ֆունկցիաներ) պետք է բաց լինեն ընդլայնման համար, փակ լինեն փոփոխությունների համար։
Շարունակենք աշխատանքը Car class-ի հետ։
Մենք ունենք մի քանի օբյեկտ,որոնք պատկանում են Car class-ին, և ուզում ենք պարզել,թե ինչ գույն ունեն դրանցից յուրաքանչյուրը։ Ենթադրենք մենք լուծում ենք այս խնդիրը getColor ֆունկցիայի միջոցով։
Այս մոտեցման ամենամեծ խնդիրը այն է, որ մեքենայի գույն որոշելու համար
ֆունկցիան վերլուծում է կոնկրետ օբյեկտներ։ getColor ֆունկցիան չի
համապատասխանում Բաց-փակ (Open-Closed) սկզբունքին, քանի որ, օրինակ, երբ նոր մեքենաներ ավելանան, դրանց գույնը որոշելու համար մենք պետք է փոփոխություն կատարենք ֆունկցիայի մեջ։ Օրինակ ավելացնենք նոր մեքենա։
Դրանից հետո մենք ստիպված կլինենք փոխել getColor ֆունկցիայի կոդը:
Ինչպես տեսնում ենք, զանգվածին նոր էլեմենտ ավելացնելիս մենք ստիպված ենք ավելացնել getColor ֆունկցիայի կոդը։ Սա շատ պարզ օրինակ է, սակայն նմանատիպ կառուցվածքով պրոյեկտներում անընդհատ պետք է ընդլայնել ֆունկցիաները։
Ինչպե՞ս համապատասխանեցնել getColor ֆունկցիան Բաց-փակ (Open-Closed)սկզբունքին։
Օրինակ այսպես՝
Նկատենք, որ Car class-ը ունի getColor աբստրակտ(վիրտուալ) մեթոդ։ Այսպիսի մոտեցման դեպքում պետք է, որ առանձին մեքենաներ նկարագրող class-ները ընդլայնեն getColor մեթոդը և նկարագրեն այն։ Արդյունքում մեքենա նկարագրող յուրաքանչյուր class կունենա իր getColor մեթոդը և բավական է կանչել getColor մեթոդը զանգվածի յուրաքանչյուր էլեմենտի համար։
Լիսկովի փոխարինման սկզբունք
(Liskov Substitution Principle)
Անհրաժեշտ է, որ ժառանգ class-ները կարողանան փոխարինել ծնող class-ներին։ Այս սկզբունքի նպատակը կայանում է նրանում, որ ժառանգ class-ները կարող են օգտագործվել ծնող class-ների փոխարեն, որոնցից նրանք ձևավորվում են՝ առանց խափանելու ծրագիրը:
Եթե պարզվում է, որ class-ի տեսակը ստուգվում է կոդով, ապա փոխարինման սկզբունքը խախտվում է: Դիտարկենք այս սկզբունքի կիրառումը ՝ «Car» class-ի հետ վերադառնալով օրինակին: Մենք կգրենք մի ֆունկցիա, որը պետք է տեղեկատվություն տա մեքենայի դռների քանակի վերաբերյալ։
Այս ֆունկցիան խախտում է Լիսկովի փոխարինման (Liskov Substitution)
սկզբունքը (ինչպես նաև Բաց-փակ (Open-Closed) սկզբունքը)։ Այս կոդը պետք է իմանա բոլոր օբյեկտների տիպերի մասին և կախված տիպից կանչի
համապատասխան ֆունկցիան։ Արդյունքում նոր մեքենա ավելացնելիս պետք է ավելացնենք ֆունկցիայի կոդը։ Որպեսզի ֆունկցիան չխախտի փոխարինման սկզբունքը, վերափոխենք այն ըստ Սթիվ Ֆենտոնի կողմից ձևակերպված պահանջների։ Եթե մեթոդները ընդունում կամ վերադարձնում են որպես պարամետր, ինչ որ class-ի տիպի օբյեկտ, ապա դրանք պետք է ընդունեն և վերադարձնեն նաև այդ class-ից ժառանգված class-ների տիպի օբյեկտներ։ Փորձենք ձևափոխել carDoorCount ֆունկցիան։
Այժմ այս ֆունկցիային չի հետաքրքրում դրան փոխանցված օբյեկտների տեսակը։ Նա ուղակի կանչում է օբյեկտների doorCount ֆունկցիան: Այն ամենը, ինչը մեր ֆունկցիան պետք է իմանա տիպերի մասին դա այն է , որ բոլոր տիպերը պետք է լինեն Car տիպի կամ ժառանգված լինեն Car-ից և Car class-ում պետք է ունենանք doorCount մեթոդը, իսկ դրա ժառանգ class-ները պետք է նկարագրեն այս մեթոդը։
Ինտերֆեյսի առանձնացման սկզբունք (Interface Segregation Principle)
Ռոբերտ Ս. Մարտինը սահմանել է այս սկզբունքը հետևյալ կերպ.
Ծրագրային ապահովման սուբյեկտները չպետք է կախված լինեն այն
մեթոդներից, որոնք նրանք չեն օգտագործում:
Ինտերֆեյսի տարանջատման սկզբունքը նշանակում է, որ չափազանց «հաստ» ինտերֆեյսները պետք է բաժանվեն փոքր և ավելի սպեցիֆիկ ինտերֆեյսների, այնպես, որ փոքր ինտերֆեյսերի ծրագրային սուբյեկտները իմանան միայն այն մեթոդների մասին, որոնց հետ նրանք պետք է աշխատեն: Արդյունքում, ինտերֆեյսի մեթոդը փոխելիս, ծրագրային սուբյեկտները, որոնք չեն օգտագործում այս մեթոդը, չպետք է փոխվեն:
Օրինակ ունենք ինտերֆեյս, որը նկարագրում է օնլայն խանութի ապրանքները։ Ենթադրենք մեր ապրանքները կարող են ունենալ պրոմոկոդ, զեղչ, ունեն ինչ որ գին, չափ և այլն։
Դիտարկենք հետևյալ ինտերֆեյսը.
Այս ինտերֆեյսը վատ է նրանով, որ իր մեջ պարունակում է չափազանց շատ
մեթոդներ։ Ինչ պետք է անենք, եթե մեր ապրանքների class-ը չի կարող ունենալ զեղչ կամ պրոմոկոդ։ Այսպիսով, որպեսզի չօգտագործվող մեթոդները ստիպված չլինենք նկարագրել class-ում ավելի լավ է բաժանենք մեր ինտերֆեյսը ավելի փոքր ինտերֆեյսների։ Այդ դեպքում կոնկրետ class-ը կիրականացնի այն ինտերֆեյսները, որոնք իրեն անհրաժեշտ են։
Կախվածության ինվերսիայի սկզբունք(Dependency Inversion Principle)
Կախվածության առարկան պետք է լինի աբստրակցիա, այլ ոչ թե կոնկրետ ինչ-որ բան:
1. Վերին մակարդակի մոդուլները չպետք է կախված լինեն ցածր մակարդակի
մոդուլներից: Երկուսն էլ պետք է կախված լինեն աբստրակցիաներից:
2. Աբստրակցիաները չպետք է կախված լինեն դետալներից: Դետալները պետք է կախված լինեն աբստրակցիաներից:
Ծրագրերի մշակման գործընթացում կա մի պահ, երբ ֆունկցիոնալը դադարում է տեղավորվել նույն մոդուլի մեջ: Երբ դա տեղի է ունենում, մենք պետք է լուծենք մոդուլային կախվածության խնդիրը: Արդյունքում, օրինակ, կարող է պարզվել, որ բարձր մակարդակի կոմպոնենտները կախված են ցածր մակարդակի կոմպոնենտներից:
Օրինակ․Դիտարկենք գնորդի կողմից պատվերի վճարումը՝
Ամեն ինչ թվում է բավականին տրամաբանական և օրինաչափ: Բայց կա մեկ
խնդիր` Customer class-ը կախված է OrderProcessor class-ից (ավելին, բաց -փակ սկզբունքը չի բավարարվում): Որպեսզի ազատվենք կոնկրետ class-ից
կախվածությունից, անհրաժեշտ է, որ Customer class-ը կախված լինի աբստրակցիայից։ Այսինքն OrderProcessorInterface ինտերֆեյսից։
Այսպիսով, Customer class-ը այժմ կախված է միայն աբստրակցիայից, իսկ
կոնկրետ իրականացումը, այսինքն մանրամասները նրա համար այնքան էլ կարևոր չեն: