Sistem
Sistemin ümumi arzu olunan xüsusiyyətləri bunlardır:- minimal mürəkkəblik - həddindən artıq mürəkkəb layihələrdən qaçınmaq lazımdır. Əsas odur ki, sadəlik və aydınlıqdır (ən yaxşı = sadə);
- baxım asanlığı - bir proqram yaratarkən, onun dəstəklənməsinə ehtiyac olduğunu xatırlamalısınız (siz olmasa belə), kod aydın və aydın olmalıdır;
- zəif birləşmə proqramın müxtəlif hissələri arasında minimum əlaqə sayıdır (OOP prinsiplərindən maksimum istifadə);
- təkrar istifadəyə yararlılıq - onun fraqmentlərindən başqa proqramlarda təkrar istifadə etmək imkanı olan sistemin layihələndirilməsi;
- daşınma qabiliyyəti - sistem asanlıqla başqa mühitə uyğunlaşdırılmalıdır;
- vahid üslub - sistemin müxtəlif fraqmentlərində vahid üslubda layihələndirilməsi;
- genişlənmə (miqyaslılıq) - sistemin əsas strukturunu pozmadan təkmilləşdirmək (bir fraqment əlavə etsəniz və ya dəyişdirsəniz, bu, qalan hissələrə təsir etməməlidir).
Sistemin dizayn mərhələləri
- Proqram təminatı sistemi - proqramın ümumi formada layihələndirilməsi.
- Alt sistemlərə/paketlərə ayırma - məntiqi olaraq ayrıla bilən hissələrin müəyyən edilməsi və onlar arasında qarşılıqlı əlaqə qaydalarının müəyyən edilməsi.
- Alt sistemlərin siniflərə bölünməsi - sistemin hissələrinin xüsusi siniflərə və interfeyslərə bölünməsi, habelə onlar arasında qarşılıqlı əlaqənin müəyyən edilməsi.
- Siniflərin metodlara bölünməsi bu sinfin tapşırığına əsaslanaraq sinif üçün lazım olan metodların tam tərifidir. Metod dizaynı - fərdi metodların funksionallığının ətraflı tərifi.
Sistemin dizaynının əsas prinsipləri və konsepsiyaları
Tənbəl başlatma idiomu Tətbiq istifadə olunana qədər obyekt yaratmağa vaxt sərf etmir, bu da işəsalma prosesini sürətləndirir və zibil yığan yükü azaldır. Ancaq bununla çox uzağa getməməlisiniz, çünki bu, modulluğun pozulmasına səbəb ola bilər. Bütün dizayn addımlarını müəyyən bir hissəyə, məsələn, əsas və ya fabrik kimi işləyən sinifə köçürməyə dəyər ola bilər . Yaxşı kodun aspektlərindən biri də tez-tez təkrarlanan kodun olmamasıdır. Bir qayda olaraq, belə kod ayrı bir sinifə yerləşdirilir ki, lazımi anda çağırıla bilsin. AOP ayrıca aspekt yönümlü proqramlaşdırmanı qeyd etmək istərdim . Bu, uç-to-end məntiqi tətbiq etməklə proqramlaşdırmadır, yəni təkrarlanan kod siniflərə - aspektlərə qoyulur və müəyyən şərtlərə çatdıqda çağırılır. Məsələn, müəyyən bir adla metoda daxil olduqda və ya müəyyən tipli dəyişənə daxil olduqda. Bəzən aspektlər çaşdırıcı ola bilər, çünki kodun haradan çağırıldığı dərhal aydın deyil, lakin buna baxmayaraq, bu çox faydalı bir funksionallıqdır. Xüsusilə, keşləmə və ya giriş zamanı: biz bu funksionallığı adi siniflərə əlavə məntiq əlavə etmədən əlavə edirik. OAP haqqında daha ətraflı burada oxuya bilərsiniz . Kent Bekə görə sadə memarlığın layihələndirilməsi üçün 4 qayda- Ekspressivlik - sinfin aydın ifadə edilmiş məqsədinə ehtiyac, düzgün adlandırma, kiçik ölçü və vahid məsuliyyət prinsipinə riayət etməklə əldə edilir (bunu aşağıda daha ətraflı nəzərdən keçirəcəyik).
- Minimum siniflər və üsullar - sinifləri mümkün qədər kiçik və bir istiqamətli bölməyə bölmək istəyinizlə, çox uzağa gedə bilərsiniz (antipattern - ov tüfəngi). Bu prinsip sistemi yığcam saxlamağı və çox uzağa getməməyi, hər asqırma üçün sinif yaratmağı tələb edir.
- Təkrarlanmanın olmaması - çaşdıran əlavə kod sistemin zəif dizaynının əlamətidir və ayrı yerə köçürülür.
- Bütün testlərin icrası - bütün sınaqlardan keçmiş sistemə nəzarət edilir, çünki hər hansı bir dəyişiklik testlərin uğursuz olmasına səbəb ola bilər ki, bu da bizə metodun daxili məntiqindəki dəyişikliyin həm də gözlənilən davranışın dəyişməsinə səbəb olduğunu göstərə bilər.
İnterfeys
Bəlkə də adekvat sinif yaratmağın ən vacib mərhələlərindən biri, sinfin həyata keçirilməsinin təfərrüatlarını gizlədən yaxşı bir abstraksiyanı təmsil edəcək və eyni zamanda bir-biri ilə aydın şəkildə uyğun gələn metodlar qrupunu təmsil edəcək adekvat interfeys yaratmaqdır. . SOLID prinsiplərindən birinə daha yaxından nəzər salaq - interfeys seqreqasiyası : müştərilər (siniflər) istifadə etməyəcəkləri lazımsız metodları tətbiq etməməlidirlər. Yəni, bu interfeysin yeganə tapşırığını yerinə yetirməyə yönəlmiş minimum sayda üsulla interfeys qurmaqdan danışırıqsa (mənim üçün bu, tək məsuliyyətə çox bənzəyir ), bir neçə kiçik yaratmaq daha yaxşıdır. bir şişirilmiş interfeys əvəzinə olanları. Xoşbəxtlikdən, bir sinif irsiyyətdə olduğu kimi birdən çox interfeys həyata keçirə bilər. İnterfeyslərin düzgün adlandırılmasını da xatırlamaq lazımdır: ad öz vəzifəsini mümkün qədər dəqiq əks etdirməlidir. Və təbii ki, nə qədər qısa olsa, bir o qədər az qarışıqlığa səbəb olacaq. Sənədləşdirmə üçün şərhlər adətən interfeys səviyyəsində yazılır , bu da öz növbəsində metodun nə etməli olduğunu, hansı arqumentləri tələb etdiyini və nəyin qaytaracağını ətraflı təsvir etməyə kömək edir.Sinif
Dərslərin daxili təşkilinə baxaq. Daha doğrusu, siniflər qurarkən əməl edilməli olan bəzi baxışlar və qaydalar. Tipik olaraq, sinif müəyyən bir ardıcıllıqla düzülmüş dəyişənlərin siyahısı ilə başlamalıdır:- ictimai statik sabitlər;
- özəl statik sabitlər;
- özəl instansiya dəyişənləri.
Sinif ölçüsü
İndi sinif ölçüsü haqqında danışmaq istərdim. SOLID - tək məsuliyyət prinsiplərindən birini xatırlayaq . Vahid məsuliyyət - vahid məsuliyyət prinsipi. Burada bildirilir ki, hər bir obyektin yalnız bir məqsədi (məsuliyyəti) var və onun bütün üsullarının məntiqi onu təmin etməyə yönəlib. Yəni, buna əsaslanaraq, böyük, qabarıq siniflərdən (təbiətinə görə antipatterndir - “ilahi obyektdir”) qaçmalıyıq və əgər sinifdə çoxlu müxtəlif, heterojen məntiq metodlarımız varsa, düşünməliyik. onu bir neçə məntiqi hissəyə (siniflərə) bölmək haqqında. Bu, öz növbəsində, kodun oxunuşunu yaxşılaşdıracaq, çünki müəyyən bir sinfin təxmini məqsədini bilsək, metodun məqsədini başa düşmək üçün çox vaxt lazım deyil. Siz həmçinin sinif adına diqqət yetirməlisiniz : o, tərkibindəki məntiqi əks etdirməlidir. Deyək ki, adı 20+ sözdən ibarət bir sinifimiz varsa, refaktorinq haqqında düşünməliyik. Özünə hörmət edən hər bir sinifdə bu qədər çox daxili dəyişənlər olmamalıdır. Əslində, hər bir metod onlardan biri və ya bir neçəsi ilə işləyir, bu da sinif daxilində daha çox birləşməyə səbəb olur (bu, tam olaraq belə olmalıdır, çünki sinif vahid bütöv olmalıdır). Nəticə etibarı ilə bir sinfin ahəngdarlığının artması onun belə azalmasına gətirib çıxarır və təbii ki, siniflərimizin sayı artır. Bəziləri üçün bu bezdiricidir; onlar müəyyən bir böyük tapşırığın necə işlədiyini görmək üçün daha çox sinifə getməlidirlər. Digər şeylər arasında, hər bir sinif digərləri ilə minimal şəkildə əlaqəli olan kiçik bir moduldur. Bu izolyasiya sinifə əlavə məntiq əlavə edərkən etməli olduğumuz dəyişikliklərin sayını azaldır.Obyektlər
İnkapsulyasiya
Burada ilk növbədə OOP- inkapsulyasiya prinsiplərindən biri haqqında danışacağıq . Beləliklə, həyata keçirilməsini gizlətmək dəyişənlər arasında metod təbəqəsi yaratmağa gəlmir (tək metodlar, alıcılar və tənzimləyicilər vasitəsilə girişi düşünmədən məhdudlaşdırmaq, yaxşı deyil, çünki bütün inkapsulyasiya nöqtəsi itirilir). Girişin gizlədilməsi abstraksiyaların formalaşmasına yönəlib, yəni sinif məlumatlarımızla işlədiyimiz ümumi konkret metodları təqdim edir. Ancaq istifadəçinin bu məlumatla necə işlədiyimizi dəqiq bilməsi lazım deyil - bu işləyir və yaxşıdır.Demeter qanunu
Siz həmçinin Demeter Qanununu nəzərdən keçirə bilərsiniz: bu, sinif və metod səviyyəsində mürəkkəbliyi idarə etməyə kömək edən kiçik qaydalar toplusudur. Beləliklə, fərz edək ki, bizim obyektimiz varCar
və onun metodu var - move(Object arg1, Object arg2)
. Demeter Qanununa görə, bu üsul çağırışla məhdudlaşır:
- obyektin özünün üsulları
Car
(başqa sözlə, bu); - -də yaradılmış obyektlərin üsulları
move
; - arqument kimi ötürülən obyektlərin üsulları -
arg1
,arg2
; - daxili obyektlərin metodları
Car
(eyni bu).
Məlumat strukturu
Məlumat strukturu əlaqəli elementlərin toplusudur. Bir obyekti məlumat strukturu kimi nəzərdən keçirərkən, mövcudluğu dolayısı ilə ifadə olunan metodlarla işlənən məlumat elementləri toplusudur. Yəni, məqsədi saxlanılan məlumatları saxlamaq və işləmək (emal etmək) olan bir obyektdir. Adi obyektdən əsas fərq ondan ibarətdir ki, obyekt mövcudluğu nəzərdə tutulan məlumat elementləri üzərində işləyən metodlar toplusudur. Başa düşürsən? Adi bir obyektdə əsas aspekt üsullardır və daxili dəyişənlər onların düzgün işləməsinə yönəldilmişdir, lakin məlumat strukturunda bunun əksinədir: üsullar burada əsas olan saxlanılan elementlərlə işləməyi dəstəkləyir və kömək edir. Məlumat strukturunun bir növü Data Transfer Object (DTO) . Bu, ümumi dəyişənlərə malik sinifdir və verilənlər bazası ilə işləyərkən məlumatları ötürən, rozetkalardan mesajların təhlili ilə işləyən və s. metodları olmayan (yaxud yalnız oxumaq/yazma metodları) var. Tipik olaraq, belə obyektlərdə verilənlər uzun müddət saxlanılmır və demək olar ki, dərhal tətbiqimizin işlədiyi obyektə çevrilir. Müəssisə, öz növbəsində, həm də məlumat strukturudur, lakin onun məqsədi tətbiqin müxtəlif səviyyələrində biznes məntiqində iştirak etməkdir, DTO isə məlumatı proqrama/tətbiqdən nəql etməkdir. DTO nümunəsi:@Setter
@Getter
@NoArgsConstructor
public class UserDto {
private long id;
private String firstName;
private String lastName;
private String email;
private String password;
}
Hər şey aydın görünür, amma burada hibridlərin mövcudluğu haqqında öyrənirik. Hibridlər mühüm məntiqi idarə etmək və daxili elementləri və onlara daxil olmaq üsullarını (almaq/quraşdırmaq) saxlamaq üçün üsulları ehtiva edən obyektlərdir. Belə obyektlər dağınıqdır və yeni metodların əlavə edilməsini çətinləşdirir. Onları istifadə etməməlisiniz, çünki onların nə üçün nəzərdə tutulduğu aydın deyil - elementləri saxlamaq və ya bir növ məntiq yerinə yetirmək. Siz burada mümkün obyekt növləri haqqında oxuya bilərsiniz .
Dəyişənlərin yaradılması prinsipləri
Dəyişənlər haqqında bir az düşünək, daha doğrusu, onların yaradılması prinsiplərinin nə ola biləcəyini düşünək:- İdeal olaraq, siz dəyişəni istifadə etməzdən əvvəl dərhal elan etməli və işə salmalısınız (onu yaratmaq və unutmaq əvəzinə).
- Mümkün olduqda, başlanğıcdan sonra dəyərinin dəyişməsinin qarşısını almaq üçün dəyişənləri yekun elan edin.
- Sayğac dəyişənləri haqqında unutmayın (adətən biz onları bir növ döngədə istifadə edirik
for
, yəni onları sıfırlamağı unutmamalıyıq, əks halda bütün məntiqimizi poza bilər). - Siz konstruktorda dəyişənləri işə salmağa çalışmalısınız.
- Obyektin istinadlı və ya istinadsız istifadəsi arasında seçim varsa (
new SomeObject()
), olmadan ( ) seçin, çünki bir dəfə istifadə edilən bu obyekt növbəti zibil yığımı zamanı silinəcək və resursları israf etməyəcək. - Dəyişənlərin ömrünü mümkün qədər qısa edin (bir dəyişənin yaradılması ilə son giriş arasındakı məsafə).
- Döngədə istifadə olunan dəyişənləri dövrəni ehtiva edən metodun əvvəlində deyil, dərhal döngədən əvvəl işə salın.
- Həmişə ən məhdud əhatə dairəsi ilə başlayın və yalnız zəruri hallarda onu genişləndirin (dəyişəni mümkün qədər yerli etməyə çalışmalısınız).
- Hər dəyişəni yalnız bir məqsəd üçün istifadə edin.
- Gizli mənaları olan dəyişənlərdən çəkinin (dəyişən iki tapşırıq arasında parçalanıb, yəni onun növü onlardan birinin həlli üçün uyğun deyil).
Metodlar
Gəlin birbaşa məntiqimizin həyata keçirilməsinə, yəni üsullara keçək.-
Birinci qayda kompaktlıqdır. İdeal olaraq, bir üsul 20 sətirdən çox olmamalıdır, buna görə də, məsələn, ictimai bir üsul əhəmiyyətli dərəcədə "şişirsə", ayrılmış məntiqi özəl metodlara köçürmək barədə düşünməlisiniz.
-
if
İkinci qayda odur ki ,else
, və s. komandalardakı bloklarwhile
yüksək səviyyədə yerləşdirilməməlidir: bu, kodun oxunuşunu əhəmiyyətli dərəcədə azaldır. İdeal olaraq, yuva iki blokdan çox olmamalıdır{}
.Bu bloklardakı kodu yığcam və sadə etmək də məqsədəuyğundur.
-
Üçüncü qayda odur ki, metod yalnız bir əməliyyat yerinə yetirməlidir. Yəni bir üsul mürəkkəb, müxtəlif məntiqi yerinə yetirirsə, onu alt metodlara bölürük. Nəticədə, metodun özü bir fasad olacaq, məqsədi bütün digər əməliyyatları düzgün qaydada çağırmaqdır.
Bəs əməliyyat ayrı bir üsul yaratmaq üçün çox sadə görünürsə? Bəli, bəzən topdan sərçə atmaq kimi görünə bilər, lakin kiçik üsullar bir sıra faydalar təmin edir:
- daha asan kodu oxumaq;
- üsullar inkişaf zamanı daha mürəkkəb olur və əgər metod əvvəlcə sadə idisə, onun funksionallığını çətinləşdirmək bir az daha asan olacaq;
- icra detallarını gizlətmək;
- kodun təkrar istifadəsini asanlaşdırmaq;
- daha yüksək kod etibarlılığı.
-
Aşağıya doğru qayda budur ki, kodu yuxarıdan aşağı oxumaq lazımdır: nə qədər aşağı olarsa, məntiqin dərinliyi nə qədər böyük olarsa və əksinə, daha yüksək olarsa, metodlar bir o qədər abstraktdır. Məsələn, keçid əmrləri olduqca yığcam və arzuolunmazdır, lakin keçiddən istifadə etmədən edə bilmirsinizsə, onu mümkün qədər aşağı səviyyəyə, ən aşağı səviyyəli üsullara köçürməyə çalışmalısınız.
-
Metod arqumentləri - neçəsi idealdır? İdeal olaraq, heç biri yoxdur)) Amma bu, həqiqətən baş verir? Bununla belə, onların mümkün qədər az olmasına çalışmalısınız, çünki onların sayı nə qədər azdırsa, bu metoddan istifadə etmək bir o qədər asandır və onu sınaqdan keçirmək bir o qədər asandır. Əgər şübhəniz varsa, çox sayda giriş arqumenti olan bir metoddan istifadə üçün bütün ssenariləri təxmin etməyə çalışın.
-
Ayrı-ayrılıqda mən bir giriş arqumenti kimi boolean bayrağı olan metodları vurğulamaq istərdim , çünki bu, təbii olaraq bu metodun birdən çox əməliyyat həyata keçirməsini nəzərdə tutur (əgər doğrudursa, biri yanlışdır - başqa). Yuxarıda yazdığım kimi, bu yaxşı deyil və mümkünsə ondan çəkinmək lazımdır.
-
Metodun çoxlu sayda daxil olan arqumentləri varsa (ekstremal dəyər 7-dir, lakin bu barədə 2-3-dən sonra düşünmək lazımdır), bəzi arqumentləri ayrıca obyektdə qruplaşdırmaq lazımdır.
-
Bir neçə oxşar üsul varsa (həddən artıq yüklənmiş) , onda oxşar parametrlər eyni ardıcıllıqla ötürülməlidir: bu, oxunaqlılığı və istifadəni artırır.
-
Parametrləri metoda ötürəndə onların hamısının istifadə olunacağına əmin olmalısınız, əks halda arqument nə üçündür? Onu interfeysdən kəsin və budur.
-
try/catch
Təbiətinə görə çox gözəl görünmür, buna görə də onu aralıq ayrı bir üsula (istisnaların idarə edilməsi üsulu) köçürmək yaxşı bir addım olardı:public void exceptionHandling(SomeObject obj) { try { someMethod(obj); } catch (IOException e) { e.printStackTrace(); } }
GO TO FULL VERSION