Tot ce trebuie să știți despre principiile solide din Java



În acest articol veți afla în detaliu despre ceea ce sunt principiile Solid în java cu exemple și importanța lor cu exemplul vieții reale.

În lumea (OOP), există multe linii directoare, modele sau principii de proiectare. Cinci dintre aceste principii sunt grupate de obicei împreună și sunt cunoscute sub acronimul SOLID. Deși fiecare dintre aceste cinci principii descrie ceva specific, ele se suprapun și astfel încât adoptarea unuia dintre ele implică sau duce la adoptarea altui. În acest articol vom înțelege principiile SOLID în Java.

Istoria principiilor SOLID în Java

Robert C. Martin a dat cinci principii de proiectare orientate pe obiecte, iar acronimul „S.O.L.I.D” este folosit pentru aceasta. Când utilizați toate principiile S.O.L.I.D într-o manieră combinată, devine mai ușor pentru dvs. să dezvoltați software care poate fi gestionat cu ușurință. Celelalte caracteristici ale utilizării S.O.L.I.D sunt:





  • Evită mirosurile de cod.
  • Cod refractor rapid.
  • Poate dezvolta software adaptiv sau agil.

Când utilizați principiul S.O.L.I.D în codare, începeți să scrieți codul care este atât eficient, cât și eficient.



Care este semnificația S.O.L.I.D?

Solid reprezintă cinci principii ale java care sunt:

  • S : Principiul responsabilității unice
  • SAU : Principiul deschis-închis
  • L : Principiul substituției Liskov
  • Eu : Principiul segregării interfeței
  • D : Principiul inversiunii dependenței

În acest blog, vom discuta în detaliu toate cele cinci principii SOLID ale Java.



Principiul de responsabilitate unică în Java

Ce spune?

Robert C. Martin o descrie ca o clasă ar trebui să aibă o singură responsabilitate.

Conform principiului responsabilității unice, ar trebui să existe un singur motiv din cauza căruia o clasă trebuie schimbată. Înseamnă că o clasă ar trebui să aibă o sarcină de făcut. Acest principiu este adesea denumit subiectiv.

Principiul poate fi bine înțeles cu un exemplu. Imaginați-vă că există o clasă care efectuează următoarele operații.

  • Conectat la o bază de date

  • Citiți câteva date din tabelele bazei de date

  • În cele din urmă, scrieți-l într-un fișier.

Ți-ai imaginat scenariul? Aici clasa are mai multe motive pentru a se schimba, iar câteva dintre ele sunt modificarea ieșirii fișierului, adoptarea unei noi baze de date. Când vorbim despre responsabilitatea principiului unic, am spune, există prea multe motive pentru care clasa să se schimbe, prin urmare, aceasta nu se încadrează corect în principiul responsabilității unice.

De exemplu, o clasă Automobile poate porni sau opri singură, dar sarcina de a o spăla aparține clasei CarWash. Într-un alt exemplu, o clasă Book are proprietăți pentru a stoca propriul nume și text. Dar sarcina de a tipări cartea trebuie să aparțină clasei Printer Book. Clasa de imprimantă de cărți s-ar putea tipări pe consolă sau pe un alt suport, dar astfel de dependențe sunt eliminate din clasa de cărți

De ce este necesar acest principiu?

Când se respectă principiul de responsabilitate unică, testarea este mai ușoară. Cu o singură responsabilitate, clasa va avea mai puține cazuri de testare. Mai puțină funcționalitate înseamnă, de asemenea, mai puține dependențe față de alte clase. Aceasta conduce la o mai bună organizare a codului, deoarece clasele mai mici și bine intenționate sunt mai ușor de căutat.

Un exemplu pentru a clarifica acest principiu:

Să presupunem că vi se cere să implementați un serviciu UserSetting în care utilizatorul poate modifica setările, dar înainte de aceasta, utilizatorul trebuie să fie autentificat. O modalitate de a implementa acest lucru ar fi:

public class UserSettingService {public void changeEmail (User user) {if (checkAccess (user)) {// Opțiune de acordare a modificării}} public boolean checkAccess (User user) {// Verificați dacă utilizatorul este valid. }}

Totul arată bine până când doriți să reutilizați codul checkAccess într-un alt loc SAU doriți să faceți modificări la modul în care se face checkAccess. În toate cele 2 cazuri, veți ajunge să schimbați aceeași clasă și, în primul caz, va trebui să utilizați UserSettingService pentru a verifica și accesul.
O modalitate de a corecta acest lucru este de a descompune UserSettingService în UserSettingService și SecurityService. Și mutați codul checkAccess în SecurityService.

public class UserSettingService {public void changeEmail (Utilizator utilizator) {if (SecurityService.checkAccess (utilizator)) {// Acordare opțiune pentru schimbare}}} public class SecurityService {public static boolean checkAccess (User user) {// verificați accesul. }}

Deschideți principiul închis în Java

Robert C. Martin îl descrie ca componentele software ar trebui să fie deschise pentru extensie, dar închise pentru modificare.

Pentru a fi precis, conform acestui principiu, o clasă ar trebui să fie scrisă în așa fel încât să își îndeplinească treaba fără cusur, fără presupunerea că oamenii în viitor vor veni pur și simplu și o vor schimba. Prin urmare, clasa ar trebui să rămână închisă pentru modificare, dar ar trebui să aibă opțiunea de a fi extinsă. Modalitățile de extindere a clasei includ:

  • Moștenind din clasă

  • Suprascrierea comportamentelor necesare din clasă

  • Extinderea anumitor comportamente ale clasei

Un exemplu excelent de principiu deschis-închis poate fi înțeles cu ajutorul browserelor. Vă amintiți să instalați extensii în browserul dvs. Chrome?

Funcția de bază a browserului Chrome este de a naviga pe diferite site-uri. Doriți să verificați gramatica atunci când scrieți un e-mail utilizând browserul Chrome? Dacă da, puteți utiliza pur și simplu extensia Gramatică, aceasta vă oferă o verificare gramaticală a conținutului.

Acest mecanism în care adăugați lucruri pentru creșterea funcționalității browserului este o extensie. Prin urmare, browserul este un exemplu perfect de funcționalitate care este deschisă pentru extensie, dar este închisă pentru modificare. În cuvinte simple, puteți îmbunătăți funcționalitatea prin adăugarea / instalarea de pluginuri în browser, dar nu puteți construi nimic nou.

De ce este necesar acest principiu?

OCP este important, deoarece cursurile pot veni la noi prin biblioteci terțe. Ar trebui să putem extinde aceste clase fără să ne îngrijorăm dacă aceste clase de bază ne pot susține extensiile. Dar moștenirea poate duce la subclase care depind de implementarea clasei de bază. Pentru a evita acest lucru, se recomandă utilizarea interfețelor. Această abstracție suplimentară duce la cuplarea liberă.

Să spunem că trebuie să calculăm suprafețe de diferite forme. Începem cu crearea unei clase pentru primul nostru dreptunghicare are 2 atribute de lungime& lățime.

public class Rectangle {public double length public double width}

Apoi creăm o clasă pentru a calcula aria acestui dreptunghicare are o metodă calculateRectangleAreacare ia dreptunghiulca parametru de intrare și își calculează aria.

public class AreaCalculator {public double calculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width}}

Până acum, bine. Să presupunem că obținem al doilea cerc de formă. Deci, vom crea cu promptitudine un nou cerc de clasăcu o singură rază de atribut.

scânteie apache în comparație cu reducerea mapelor hadoop
public class Circle {public double radius}

Apoi modificăm Areacalculatorclasă pentru a adăuga calcule de cerc printr-o nouă metodă calculateCircleaArea ()

public class AreaCalculator {public double calculateRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width} public double calculateCircleArea (Circle circle) {return (22/7) * circle.radius * circle.radius}}

Cu toate acestea, rețineți că au existat defecte în modul în care ne-am proiectat soluția de mai sus.

Să spunem că avem un nou pentagon de formă. În acest caz, vom ajunge din nou să modificăm clasa AreaCalculator. Pe măsură ce tipurile de forme cresc, acest lucru devine mai dezordonat pe măsură ce AreaCalculator continuă să se schimbe și orice consumator din această clasă va trebui să își actualizeze bibliotecile care conțin AreaCalculator. Ca rezultat, clasa AreaCalculator nu va fi bazată (finalizată) cu garanție, deoarece de fiecare dată când apare o nouă formă va fi modificată. Deci, acest design nu este închis pentru modificare.

AreaCalculator va trebui să continue să își adauge logica de calcul în metode mai noi. Nu extindem cu adevărat sfera de aplicare a formelor, ci mai degrabă facem o soluție de bucată (bucată cu bucată) pentru fiecare formă adăugată.

Modificarea proiectului de mai sus pentru a respecta principiul deschis / închis:

Să vedem acum un design mai elegant, care rezolvă defectele din designul de mai sus, respectând principiul deschis / închis. În primul rând vom face extensibil designul. Pentru aceasta trebuie mai întâi să definim un tip de bază Shape și să punem Circle & Rectangle să implementeze interfața Shape.

interfață publică Shape {public double calculateArea ()} public class Rectangle implements Shape {double length double width public double calculateArea () {return length * width}} public class Circle implements Shape {public double radius public double calculateArea () {return (22 / 7) * raza * raza}}

Există o interfață de bază Shape. Toate formele implementează acum interfața de bază Shape. Interfața Shape are o metodă abstractă calculateArea (). Ambele cerc și dreptunghi oferă propria lor implementare suprascrisă a metodei calculateArea () folosind propriile atribute.
Am adus un grad de extensibilitate, deoarece formele sunt acum un exemplu de interfețe Shape. Acest lucru ne permite să folosim Shape în loc de clase individuale
Ultimul punct menționat mai sus, consumatorul acestor forme. În cazul nostru, consumatorul va fi clasa AreaCalculator care acum ar arăta astfel.

public class AreaCalculator {public double calculateShapeArea (Shape shape) {return shape.calculateArea ()}}

Acest Calculator de zonăclasa elimină acum complet defectele noastre de design menționate mai sus și oferă o soluție curată care aderă la principiul deschis-închis. Să continuăm cu alte principii SOLID din Java

Principiul de substituție Liskov în Java

Robert C. Martin îl descrie ca tipurile derivate trebuie să fie complet substituibile tipurilor lor de bază.

Principiul substituției Liskov presupune că q (x) este o proprietate, demonstrabilă cu privire la entitățile de x care aparțin tipului T. Acum, conform acestui principiu, q (y) ar trebui să fie acum demonstrabil pentru obiectele y care aparțin tipului S și S este de fapt un subtip de T. Ești acum confuz și nu știi ce înseamnă de fapt principiul substituției Liskov? Definiția acestuia ar putea fi puțin complexă, dar, de fapt, este destul de ușoară. Singurul lucru este că fiecare subclasă sau clasă derivată ar trebui să poată fi înlocuită cu părinții sau clasa de bază.

Puteți spune că este un principiu unic orientat pe obiecte. Principiul poate fi în continuare simplificat de un tip de copil dintr-un anumit tip de părinte, fără a face nicio complicație sau aruncarea în aer ar trebui să aibă capacitatea de a înlocui părintele respectiv.

De ce este necesar acest principiu?

Astfel se evită folosirea greșită a moștenirii. Ne ajută să ne conformăm relației „este-o”. Putem spune, de asemenea, că subclasele trebuie să îndeplinească un contract definit de clasa de bază. În acest sens, este legat deProiectare prin contractaceasta a fost descrisă pentru prima dată de Bertrand Meyer. De exemplu, este tentant să spunem că un cerc este un tip de elipsă, dar cercurile nu au două focare sau axe majore / minore.

LSP este explicat în mod popular folosind exemplul pătrat și dreptunghi. dacă presupunem o relație ISA între pătrat și dreptunghi. Astfel, numim „Pătratul este un dreptunghi”. Codul de mai jos reprezintă relația.

public class Rectangle {private int length private int width public int getLength () {return length} public void setLength (int length) {this.length = length} public int getBreadth () {return wide} public void setBreadth (int wide) { this.breadth = lățime} public int getArea () {return this.length * this.breadth}}

Mai jos este codul pentru Square. Rețineți că pătratul extinde dreptunghiul.

public class Square extinde Rectangle {public void setBreadth (int lățime) {super.setBreadth (lățime) super.setLength (lățime)} public void setLength (int lungime) {super.setLength (lungime) super.setBreadth (lungime)}}

În acest caz, încercăm să stabilim o relație ISA între pătrat și dreptunghi, astfel încât apelarea „pătratului este un dreptunghi” în codul de mai jos ar începe să se comporte în mod neașteptat dacă se trece o instanță a pătratului. O eroare de afirmare va fi aruncată în cazul verificării „zonei” și verificării „lățimii”, deși programul se va termina pe măsură ce eroarea de afirmare este aruncată din cauza eșecului verificării zonei.

public class LSPDemo {public void calculateArea (Rectangle r) {r.setBreadth (2) r.setLength (3) asert r.getArea () == 6: printError ('area', r) assert r.getLength () == 3: printError ('lungime', r) afirmă r.getBreadth () == 2: printError ('lățime', r)} private String printError (String errorIdentifer, Rectangle r) {return 'Valoare neașteptată a' + errorIdentifer + ' de exemplu '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // Se trece o instanță de dreptunghi lsp.calculateArea (nou dreptunghi ()) // O instanță de pătrat este trecută lsp.calculateArea (nou pătrat ())}}

Clasa demonstrează principiul de înlocuire Liskov (LSP) Conform principiului, funcțiile care utilizează referințe la clasele de bază trebuie să poată utiliza obiecte din clasa derivată fără să știe.

Astfel, în exemplul prezentat mai jos, funcția calculateArea care folosește referința „Rectangle” ar trebui să poată utiliza obiectele din clasa derivată precum Square și să îndeplinească cerința propusă de definiția Rectangle. Trebuie remarcat faptul că, conform definiției dreptunghiului, următoarele trebuie să fie întotdeauna valabile, având în vedere datele de mai jos:

  1. Lungimea trebuie să fie întotdeauna egală cu lungimea transmisă ca intrare la metoda, setLength
  2. Lățimea trebuie să fie întotdeauna egală cu lățimea transmisă ca intrare la metodă, setBreadth
  3. Suprafața trebuie să fie întotdeauna egală cu produsul de lungime și lățime

În cazul în care încercăm să stabilim o relație ISA între pătrat și dreptunghi, astfel încât să numim „pătratul este un dreptunghi”, codul de mai sus ar începe să se comporte în mod neașteptat dacă o instanță a pătratului este trecută. pentru lățime, deși programul se va termina pe măsură ce eroarea de afirmare este aruncată din cauza eșecului verificării zonei.

Clasa Square nu are nevoie de metode precum setBreadth sau setLength. Clasa LSPDemo ar trebui să cunoască detaliile claselor derivate de dreptunghi (cum ar fi Square) pentru a codifica corespunzător pentru a evita erorile de aruncare. Modificarea codului existent încalcă principiul deschis-închis.

Principiul de separare a interfeței

Robert C. Martin o descrie ca clienții nu ar trebui să fie obligați să implementeze metode inutile pe care nu le vor folosi.

ConformPrincipiul segregării interfețeiun client, indiferent de ceea ce nu ar trebui să fie obligat niciodată să implementeze o interfață pe care nu o folosește sau clientul nu ar trebui să fie obligat să depindă niciodată de nicio metodă, care nu este utilizată de aceștia. interfețe, care sunt mici, dar specifice pentru client în loc de interfețe monolitice și mai mari. Pe scurt, ar fi rău să forțezi clientul să depindă de un anumit lucru, de care nu au nevoie.

De exemplu, o singură interfață de înregistrare pentru scrierea și citirea jurnalelor este utilă pentru o bază de date, dar nu pentru o consolă. Citirea jurnalelor nu are sens pentru un jurnal consolă. Continuăm cu acest articol Principii SOLID în Java.

De ce este necesar acest principiu?

Să spunem că există o interfață de restaurant care conține metode pentru acceptarea comenzilor de la clienți online, clienți dial-in sau telefonici și clienți walk-in. De asemenea, conține metode de gestionare a plăților online (pentru clienții online) și a plăților în persoană (pentru clienții care intră, precum și pentru clienții de telefonie atunci când comanda lor este livrată acasă).

Acum permiteți-ne să creăm o interfață Java pentru restaurant și să o numim RestaurantInterface.java.

interfață publică RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

Există 5 metode definite în RestaurantInterface, care sunt pentru acceptarea comenzilor online, preluarea comenzilor telefonice, acceptarea comenzilor de la un client accesibil, acceptarea plăților online și acceptarea plății în persoană.

Să începem prin implementarea RestaurantInterface pentru clienții online ca OnlineClientImpl.java

public class OnlineClientImpl implementează RestaurantInterface {public void acceptOnlineOrder () {// logică pentru plasarea comenzii online} public void takeTelephoneOrder () {// Nu se aplică pentru comanda online aruncați noi UnsupportedOperationException ()} public void payOnline () {// logică pentru plată online} public void walkInCustomerOrder () {// Nu se aplică pentru comanda online aruncă UnsupportedOperationException ()} public void payInPerson () {// Nu se aplică pentru comanda online aruncă o nouă UnsupportedOperationException ()}}
  • Deoarece codul de mai sus (OnlineClientImpl.java) este pentru comenzi online, aruncați UnsupportedOperationException.

  • Clienții online, telefonici și walk-in utilizează implementarea RestaurantInterface specifică fiecăruia dintre ei.

  • Clasele de implementare pentru clientul telefonic și clientul Walk-in vor avea metode neacceptate.

  • Deoarece cele 5 metode fac parte din RestaurantInterface, clasele de implementare trebuie să le implementeze pe toate 5.

  • Metodele pe care fiecare dintre clasele de implementare le aruncă UnsupportedOperationException. După cum puteți vedea clar - implementarea tuturor metodelor este ineficientă.

  • Orice modificare a oricăreia dintre metodele din RestaurantInterface va fi propagată tuturor claselor de implementare. Menținerea codului începe apoi să devină foarte greoaie, iar efectele de regresie ale modificărilor vor crește în continuare.

    ce este sincronizarea în java
  • RestaurantInterface.java încalcă principiul de responsabilitate unică, deoarece logica plăților, precum și cea pentru plasarea comenzilor, sunt grupate într-o singură interfață.

Pentru a depăși problemele menționate mai sus, aplicăm principiul de separare a interfeței pentru a refactura designul de mai sus.

  1. Separați funcționalitățile de plată și plasare a comenzilor în două interfețe lean separate, PaymentInterface.java și OrderInterface.java.

  2. Fiecare dintre clienți utilizează câte o implementare de PaymentInterface și OrderInterface. De exemplu - OnlineClient.java folosește OnlinePaymentImpl și OnlineOrderImpl și așa mai departe.

  3. Principiul de responsabilitate unică este acum atașat ca interfață de plată (PaymentInterface.java) și interfață de comandă (OrderInterface).

  4. Modificarea oricărei interfețe de comandă sau de plată nu o afectează pe cealaltă. Ele sunt independente acum.Nu va fi nevoie să faceți nicio implementare falsă sau să lansați o excepție UnsupportedOperationException, deoarece fiecare interfață are doar metode pe care le va folosi întotdeauna.

După aplicarea ISP

Principiul inversiunii dependenței

Robert C. Martin îl descrie deoarece depinde de abstracții, nu de concreții. Potrivit acestuia, modulul de nivel înalt nu trebuie să se bazeze niciodată pe niciun modul de nivel scăzut. de exemplu

Mergi la un magazin local pentru a cumpăra ceva și te hotărăști să îl plătești folosind cardul tău de debit. Așadar, atunci când dați grefierul pentru efectuarea plății, grefierul nu se deranjează să verifice ce fel de card ați dat.

Chiar dacă ați dat un card Visa, acesta nu va scoate un aparat Visa pentru a vă glisa cardul. Tipul de card de credit sau card de debit pe care îl aveți pentru plată nu contează nici măcar că îl va glisa pur și simplu. Deci, în acest exemplu, puteți vedea că atât dvs., cât și grefierul depindeți de abstractizarea cardului de credit și nu sunteți îngrijorat de specificul cardului. Aceasta este principiul inversării dependenței.

De ce este necesar acest principiu?

Acesta permite unui programator să elimine dependențele codificate, astfel încât aplicația să devină cuplată și extensibilă.

public class Student {private Address address public student () {address = new Address ()}}

În exemplul de mai sus, clasa Student necesită un obiect Adresă și este responsabilă pentru inițializarea și utilizarea obiectului Adresă. Dacă clasa Adresă este schimbată în viitor, atunci trebuie să facem modificări și în clasa Student. Acest lucru face cuplarea strânsă între obiectele Student și Address. Putem rezolva această problemă folosind modelul de proiectare a inversării dependenței. Adică obiectul Adresă va fi implementat independent și va fi furnizat Studentului atunci când Studentul este instanțiat utilizând inversiunea dependenței bazată pe constructor sau pe setter.

Cu aceasta, ajungem la sfârșitul acestor principii SOLID în Java.

Verificați de Edureka, o companie de învățare online de încredere, cu o rețea de peste 250.000 de elevi mulțumiți răspândiți pe tot globul. Cursul de formare și certificare Java J2EE și SOA al Edureka este conceput pentru studenți și profesioniști care doresc să fie dezvoltator Java. Cursul este conceput pentru a vă oferi un început important în programarea Java și pentru a vă instrui atât pentru conceptele Java de bază, cât și pentru cele avansate, împreună cu diverse cadre Java, cum ar fi Hibernate & Spring.

Ai o întrebare pentru noi? Vă rugăm să o menționați în secțiunea de comentarii a acestui blog „Principiile SOLID în Java” și vă vom contacta cât mai curând posibil.